lex-agentic-social 0.1.15 → 0.1.17

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: 4ae718ace3eafb04ea8f811bb5cf0d9ef384e6b63887f1423c8461b04269a524
4
- data.tar.gz: 84e78ce21707ba78c4f90dc5168a99ca494fa4610d12cc609a72f5664cf43bee
3
+ metadata.gz: 2fc82cf70d0df9c909097bbed4d53fc58e64e2ca8904a49c6d7abf030fc3b2c6
4
+ data.tar.gz: 41e0f3966d50d002ccd5cf3a96d488b7bb1d2208cc4f36b931a5fd311786c80a
5
5
  SHA512:
6
- metadata.gz: 14f3884ae4814a76e9872081d132cd4b9b3fbf5a3dcf247541a67e5cf6c4d3a152e071753de8fc45a50a99cdb83df78dc2a8019f1fb949dea1f628a4da7870af
7
- data.tar.gz: 3f991289c88888c67ddb146e094ea07f39f7fd79698c35c1b8b4bbb01cf29c74e6b256238c4b0f6b2c411eab6a070893db5350a99c8c1f0e7775461eca2c0ce4
6
+ metadata.gz: e013c357c5281df840b02a4ec06d16fee47d63af742b97ae1a6fe132612b66f6c8464b0316b1d564e6cc3927da03b8b21629500fc52685206af0ef28649865f6
7
+ data.tar.gz: 01a2eb7ebda54716bfb68542f22622aedf96525b98809a84d758f1fd2c8fc44d7eb6e5a02cfbf1354da466cbfebe786b99fed33212d537df1bb9cc1963bb7eb2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.17] - 2026-05-15
4
+ ### Fixed
5
+ - Social LLM enhancers now send explicit system and user messages to native `Legion::LLM.chat` dispatch instead of opening legacy nil-input chat sessions.
6
+ - Calibration LLM preference upsert now passes `access_scope: 'private'` and `identity_principal_id: nil` to prevent daemon process identity injection on personal data writes.
7
+
8
+ ## [0.1.16] - 2026-05-07
9
+ ### Fixed
10
+ - Calibration preference storage now handles symbol-keyed parsed LLM preferences without dropping domain or value.
11
+ - Consent restore preserves pending approval fields after local DB reloads.
12
+ - Shadow AI scans flag scan failures as issues, conflict stale checks parse restored string timestamps, and moral reasoning reinforcement uses dilemma severity.
13
+
3
14
  ## [0.1.15] - 2026-04-27
4
15
  ### Fixed
5
16
  - Stop social calibration partner-knowledge promotion when Legion is shutting down or Apollo Local becomes unavailable between promotable tag groups
@@ -194,14 +194,20 @@ module Legion
194
194
 
195
195
  base_tags = %w[partner preference llm_inference]
196
196
  preferences.each do |pref|
197
+ domain = pref[:domain] || pref['domain']
198
+ value = pref[:value] || pref['value']
199
+ confidence = pref[:confidence] || pref['confidence'] || 0.65
197
200
  content = Legion::JSON.dump({
198
- 'domain' => pref['domain'],
199
- 'value' => pref['value'],
201
+ 'domain' => domain,
202
+ 'value' => value,
200
203
  'source' => 'llm_inference',
201
- 'confidence' => pref['confidence'] || 0.65
204
+ 'confidence' => confidence
202
205
  })
203
- tags = base_tags + ["preference:#{pref['domain']}"]
204
- Legion::Apollo::Local.upsert(content: content, tags: tags, confidence: pref['confidence'] || 0.65)
206
+ tags = base_tags + ["preference:#{domain}"]
207
+ Legion::Apollo::Local.upsert(content: content, tags: tags,
208
+ confidence: confidence,
209
+ access_scope: 'private',
210
+ identity_principal_id: nil)
205
211
  end
206
212
  end
207
213
  end
@@ -57,9 +57,14 @@ module Legion
57
57
  content = response&.message&.dig(:content)
58
58
  ::Struct.new(:content).new(content) if content
59
59
  else
60
- chat = Legion::LLM.chat
61
- chat.with_instructions(SYSTEM_PROMPT)
62
- chat.ask(prompt)
60
+ response = Legion::LLM.chat(
61
+ message: [
62
+ { role: 'system', content: SYSTEM_PROMPT },
63
+ { role: 'user', content: prompt }
64
+ ],
65
+ caller: { extension: 'lex-agentic-social', mode: :conflict }
66
+ )
67
+ response.respond_to?(:content) ? response : response.ask(prompt)
63
68
  end
64
69
  end
65
70
  private_class_method :llm_ask
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'time'
4
+
3
5
  module Legion
4
6
  module Extensions
5
7
  module Agentic
@@ -70,12 +72,12 @@ module Legion
70
72
 
71
73
  def check_stale_conflicts(**)
72
74
  active = conflict_log.active_conflicts
73
- stale = active.select { |c| Time.now.utc - c[:created_at] > Helpers::Severity::STALE_CONFLICT_TIMEOUT }
75
+ stale = active.select { |c| conflict_age_seconds(c) > Helpers::Severity::STALE_CONFLICT_TIMEOUT }
74
76
  stale.each do |c|
75
77
  message = 'conflict marked stale — no resolution after 24h'
76
78
 
77
79
  if Helpers::LlmEnhancer.available?
78
- age_hours = (Time.now.utc - c[:created_at]) / 3600.0
80
+ age_hours = conflict_age_seconds(c) / 3600.0
79
81
  analysis = Helpers::LlmEnhancer.analyze_stale_conflict(
80
82
  description: c[:description],
81
83
  severity: c[:severity],
@@ -103,6 +105,12 @@ module Legion
103
105
  def conflict_log
104
106
  @conflict_log ||= Helpers::ConflictLog.new
105
107
  end
108
+
109
+ def conflict_age_seconds(conflict)
110
+ created_at = conflict[:created_at]
111
+ created_at = Time.parse(created_at) if created_at.is_a?(String)
112
+ Time.now.utc - created_at
113
+ end
106
114
  end
107
115
  end
108
116
  end
@@ -170,14 +170,14 @@ module Legion
170
170
  []
171
171
  end
172
172
 
173
- @domains[key] = {
173
+ @domains[key].merge!(
174
174
  tier: row[:tier].to_sym,
175
175
  success_count: row[:success_count].to_i,
176
176
  failure_count: row[:failure_count].to_i,
177
177
  total_actions: row[:total_actions].to_i,
178
178
  last_changed_at: row[:last_changed_at],
179
179
  history: history
180
- }
180
+ )
181
181
  end
182
182
  rescue StandardError => e
183
183
  Legion::Logging.warn "[consent] load_from_local failed: #{e.message}"
@@ -14,7 +14,8 @@ module Legion
14
14
  unregistered = installed - registered
15
15
  { installed: installed.size, registered: registered.size, unregistered: unregistered }
16
16
  rescue StandardError => e
17
- { installed: 0, registered: 0, unregistered: [], error: e.message }
17
+ Legion::Logging.warn("[governance:shadow_ai] extension scan failed: #{e.message}")
18
+ { installed: 0, registered: 0, unregistered: [], scan_failed: true, error: e.message }
18
19
  end
19
20
 
20
21
  def check_llm_bypass_indicators(**)
@@ -47,7 +48,8 @@ module Legion
47
48
  bypass = check_llm_bypass_indicators
48
49
  compliance = check_airb_compliance
49
50
 
50
- has_issues = extensions[:unregistered]&.any? || bypass[:bypassed] || compliance[:non_compliant]&.any?
51
+ has_issues = extensions[:scan_failed] || extensions[:unregistered]&.any? ||
52
+ bypass[:bypassed] || compliance[:non_compliant]&.any?
51
53
  emit_shadow_event(extensions, bypass, compliance) if has_issues
52
54
 
53
55
  { extensions: extensions, bypass: bypass, compliance: compliance, issues_found: has_issues }
@@ -50,9 +50,14 @@ module Legion
50
50
  content = response&.message&.dig(:content)
51
51
  ::Struct.new(:content).new(content) if content
52
52
  else
53
- chat = Legion::LLM.chat
54
- chat.with_instructions(SYSTEM_PROMPT)
55
- chat.ask(prompt)
53
+ response = Legion::LLM.chat(
54
+ message: [
55
+ { role: 'system', content: SYSTEM_PROMPT },
56
+ { role: 'user', content: prompt }
57
+ ],
58
+ caller: { extension: 'lex-agentic-social', mode: :moral_reasoning }
59
+ )
60
+ response.respond_to?(:content) ? response : response.ask(prompt)
56
61
  end
57
62
  end
58
63
  private_class_method :llm_ask
@@ -117,7 +117,7 @@ module Legion
117
117
  return { success: false, reason: :invalid_option } unless chosen
118
118
 
119
119
  dilemma.resolve(option_id: option_id, reasoning: reasoning, framework: framework)
120
- reinforce_chosen_foundations(chosen)
120
+ reinforce_chosen_foundations(chosen, severity: dilemma.severity)
121
121
  weaken_unchosen_foundations(dilemma.options, option_id)
122
122
  add_history(type: :resolution, dilemma_id: dilemma_id, option_id: option_id,
123
123
  framework: framework, severity: dilemma.severity)
@@ -210,9 +210,9 @@ module Legion
210
210
  end
211
211
  end
212
212
 
213
- def reinforce_chosen_foundations(chosen_option)
213
+ def reinforce_chosen_foundations(chosen_option, severity:)
214
214
  chosen_option.fetch(:foundations, []).each do |fid|
215
- @foundations[fid]&.reinforce(amount: chosen_option.fetch(:severity, 1.0))
215
+ @foundations[fid]&.reinforce(amount: severity)
216
216
  end
217
217
  end
218
218
 
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Social
7
- VERSION = '0.1.15'
7
+ VERSION = '0.1.17'
8
8
  end
9
9
  end
10
10
  end
@@ -70,6 +70,24 @@ RSpec.describe Legion::Extensions::Agentic::Social::Calibration::Runners::Calibr
70
70
  result = client.extract_preferences_via_llm
71
71
  expect(result[:skipped]).to eq(:llm_unavailable)
72
72
  end
73
+
74
+ it 'stores symbol-keyed parsed preferences with domain and value intact' do
75
+ mock_local = double('apollo_local')
76
+ stub_const('Legion::Apollo', Module.new)
77
+ stub_const('Legion::Apollo::Local', mock_local)
78
+ allow(mock_local).to receive(:started?).and_return(true)
79
+ allow(mock_local).to receive(:upsert)
80
+
81
+ client.send(:store_llm_preferences, [{ domain: 'tone', value: 'concise', confidence: 0.8 }])
82
+
83
+ expect(mock_local).to have_received(:upsert) do |args|
84
+ content = Legion::JSON.load(args[:content])
85
+ expect(content[:domain]).to eq('tone')
86
+ expect(content[:value]).to eq('concise')
87
+ expect(args[:tags]).to include('preference:tone')
88
+ expect(args[:confidence]).to eq(0.8)
89
+ end
90
+ end
73
91
  end
74
92
 
75
93
  describe '#retrieve_from_memory' do
@@ -182,4 +200,52 @@ RSpec.describe Legion::Extensions::Agentic::Social::Calibration::Runners::Calibr
182
200
  expect(result[:results]).to have_key(:promotion)
183
201
  end
184
202
  end
203
+
204
+ describe '#store_llm_preferences' do
205
+ let(:preferences) do
206
+ [{ 'domain' => 'communication', 'value' => 'direct', 'confidence' => 0.8 }]
207
+ end
208
+
209
+ before do
210
+ mock_local = double('apollo_local')
211
+ stub_const('Legion::Apollo', Module.new)
212
+ stub_const('Legion::Apollo::Local', mock_local)
213
+ allow(mock_local).to receive(:started?).and_return(true)
214
+ allow(mock_local).to receive(:upsert).and_return({ success: true })
215
+ end
216
+
217
+ it 'passes access_scope: private to Apollo::Local.upsert' do
218
+ mock_local = Legion::Apollo::Local
219
+ client.send(:store_llm_preferences, preferences)
220
+ expect(mock_local).to have_received(:upsert).with(
221
+ hash_including(access_scope: 'private')
222
+ )
223
+ end
224
+
225
+ it 'passes content and tags to Apollo::Local.upsert' do
226
+ mock_local = Legion::Apollo::Local
227
+ client.send(:store_llm_preferences, preferences)
228
+ expect(mock_local).to have_received(:upsert).with(
229
+ hash_including(
230
+ content: a_string_including('direct'),
231
+ tags: array_including('preference', 'preference:communication')
232
+ )
233
+ )
234
+ end
235
+
236
+ it 'does not inject process identity as the owner' do
237
+ stub_const('Legion::Identity::Process', Module.new do
238
+ extend self
239
+
240
+ define_method(:identity_hash) do
241
+ { canonical_name: 'daemon', db_principal_id: 999, db_identity_id: 888 }
242
+ end
243
+ end)
244
+ mock_local = Legion::Apollo::Local
245
+ client.send(:store_llm_preferences, preferences)
246
+ expect(mock_local).to have_received(:upsert).with(
247
+ hash_including(identity_principal_id: nil)
248
+ )
249
+ end
250
+ end
185
251
  end
@@ -112,6 +112,53 @@ RSpec.describe Legion::Extensions::Agentic::Social::Conflict::Helpers::LlmEnhanc
112
112
  expect(result).to be_nil
113
113
  end
114
114
  end
115
+
116
+ context 'when LLM returns a native content response (direct dispatch path)' do
117
+ let(:native_response) do
118
+ double(content: "OUTCOME: resolved\nNOTES: Native dispatch resolved the conflict directly.")
119
+ end
120
+
121
+ before do
122
+ allow(Legion::LLM).to receive(:chat).and_return(native_response)
123
+ end
124
+
125
+ it 'calls Legion::LLM.chat with system and user messages' do
126
+ expect(Legion::LLM).to receive(:chat).with(
127
+ hash_including(
128
+ message: array_including(
129
+ hash_including(role: 'system'),
130
+ hash_including(role: 'user')
131
+ )
132
+ )
133
+ ).and_return(native_response)
134
+ described_class.suggest_resolution(
135
+ description: 'Agent and human disagree',
136
+ severity: :medium,
137
+ exchanges: []
138
+ )
139
+ end
140
+
141
+ it 'calls Legion::LLM.chat with caller metadata' do
142
+ expect(Legion::LLM).to receive(:chat).with(
143
+ hash_including(caller: { extension: 'lex-agentic-social', mode: :conflict })
144
+ ).and_return(native_response)
145
+ described_class.suggest_resolution(
146
+ description: 'Agent and human disagree',
147
+ severity: :medium,
148
+ exchanges: []
149
+ )
150
+ end
151
+
152
+ it 'uses the content directly without calling ask' do
153
+ expect(native_response).not_to receive(:ask)
154
+ result = described_class.suggest_resolution(
155
+ description: 'Agent and human disagree',
156
+ severity: :medium,
157
+ exchanges: []
158
+ )
159
+ expect(result[:suggested_outcome]).to eq(:resolved)
160
+ end
161
+ end
115
162
  end
116
163
 
117
164
  describe '.analyze_stale_conflict' do
@@ -185,5 +232,46 @@ RSpec.describe Legion::Extensions::Agentic::Social::Conflict::Helpers::LlmEnhanc
185
232
  expect(result).to be_nil
186
233
  end
187
234
  end
235
+
236
+ context 'when LLM returns a native content response (direct dispatch path)' do
237
+ let(:native_response) do
238
+ double(content: "RECOMMENDATION: escalate\nANALYSIS: Native dispatch recommends escalation.")
239
+ end
240
+
241
+ before do
242
+ allow(Legion::LLM).to receive(:chat).and_return(native_response)
243
+ end
244
+
245
+ it 'calls Legion::LLM.chat with system and user messages' do
246
+ expect(Legion::LLM).to receive(:chat).with(
247
+ hash_including(
248
+ message: array_including(
249
+ hash_including(role: 'system'),
250
+ hash_including(role: 'user')
251
+ )
252
+ )
253
+ ).and_return(native_response)
254
+ described_class.analyze_stale_conflict(
255
+ description: 'Stale safety concern', severity: :high, age_hours: 40.0, exchange_count: 1
256
+ )
257
+ end
258
+
259
+ it 'calls Legion::LLM.chat with caller metadata' do
260
+ expect(Legion::LLM).to receive(:chat).with(
261
+ hash_including(caller: { extension: 'lex-agentic-social', mode: :conflict })
262
+ ).and_return(native_response)
263
+ described_class.analyze_stale_conflict(
264
+ description: 'Stale safety concern', severity: :high, age_hours: 40.0, exchange_count: 1
265
+ )
266
+ end
267
+
268
+ it 'uses the content directly without calling ask' do
269
+ expect(native_response).not_to receive(:ask)
270
+ result = described_class.analyze_stale_conflict(
271
+ description: 'Stale safety concern', severity: :high, age_hours: 40.0, exchange_count: 1
272
+ )
273
+ expect(result[:recommendation]).to eq(:escalate)
274
+ end
275
+ end
188
276
  end
189
277
  end
@@ -78,6 +78,17 @@ RSpec.describe Legion::Extensions::Agentic::Social::Conflict::Runners::Conflict
78
78
  expect(result[:stale_ids]).to include(c[:conflict_id])
79
79
  end
80
80
 
81
+ it 'detects stale conflicts restored with string created_at values' do
82
+ c = client.register_conflict(parties: %w[a b], severity: :medium, description: 'old')
83
+ conflict = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]]
84
+ conflict[:created_at] = (Time.now.utc - (Legion::Extensions::Agentic::Social::Conflict::Helpers::Severity::STALE_CONFLICT_TIMEOUT + 1)).iso8601
85
+
86
+ result = client.check_stale_conflicts
87
+
88
+ expect(result[:stale_count]).to eq(1)
89
+ expect(result[:stale_ids]).to include(c[:conflict_id])
90
+ end
91
+
81
92
  it 'does not include resolved conflicts in stale check' do
82
93
  c = client.register_conflict(parties: %w[a b], severity: :low, description: 'resolved')
83
94
  conflict = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]]
@@ -122,6 +122,25 @@ RSpec.describe 'lex-consent local SQLite persistence' do
122
122
  expect(map.domains['email'][:total_actions]).to eq(6)
123
123
  end
124
124
 
125
+ it 'restores pending fields with default nil values' do
126
+ db[:consent_domains].insert(
127
+ domain_key: 'email',
128
+ tier: 'consult',
129
+ success_count: 1,
130
+ failure_count: 0,
131
+ total_actions: 1,
132
+ history: '[]'
133
+ )
134
+
135
+ map = Legion::Extensions::Agentic::Social::Consent::Helpers::ConsentMap.new
136
+ expect(map.pending?('email')).to be false
137
+ expect(map.domains['email']).to include(
138
+ pending_tier: nil,
139
+ pending_since: nil,
140
+ pending_requested_by: nil
141
+ )
142
+ end
143
+
125
144
  it 'restores history as an array of hashes with symbol keys' do
126
145
  history_json = JSON.generate([{ 'from' => 'consult', 'to' => 'act_notify', 'at' => Time.now.utc.to_s }])
127
146
  db[:consent_domains].insert(
@@ -75,5 +75,20 @@ RSpec.describe Legion::Extensions::Agentic::Social::Governance::Runners::ShadowA
75
75
  expect(result[:issues_found]).to be_falsey
76
76
  expect(result[:extensions][:installed]).to eq(5)
77
77
  end
78
+
79
+ it 'treats extension scan failure as an issue' do
80
+ allow(host).to receive(:scan_unregistered_extensions).and_return(
81
+ { installed: 0, registered: 0, unregistered: [], scan_failed: true, error: 'Gemfile not found' }
82
+ )
83
+ allow(host).to receive(:check_llm_bypass_indicators).and_return(
84
+ { indicators: [], bypassed: false }
85
+ )
86
+ allow(host).to receive(:check_airb_compliance).and_return(
87
+ { checked: 0, source: :unavailable }
88
+ )
89
+
90
+ result = host.full_scan
91
+ expect(result[:issues_found]).to be true
92
+ end
78
93
  end
79
94
  end
@@ -150,6 +150,57 @@ RSpec.describe Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Llm
150
150
  expect(result).to be_nil
151
151
  end
152
152
  end
153
+
154
+ context 'when LLM returns a native content response (direct dispatch path)' do
155
+ let(:native_response) do
156
+ content = <<~TEXT
157
+ REASONING: Native dispatch evaluated this action as broadly positive.
158
+ IMPACT: care=0.4 | fairness=0.3 | loyalty=0.1 | authority=0.0 | sanctity=0.2 | liberty=0.1
159
+ TEXT
160
+ double('native_response', content: content)
161
+ end
162
+
163
+ before do
164
+ allow(Legion::LLM).to receive(:chat).and_return(native_response)
165
+ end
166
+
167
+ it 'calls Legion::LLM.chat with system and user messages' do
168
+ expect(Legion::LLM).to receive(:chat).with(
169
+ hash_including(
170
+ message: array_including(
171
+ hash_including(role: 'system'),
172
+ hash_including(role: 'user')
173
+ )
174
+ )
175
+ ).and_return(native_response)
176
+ enhancer.evaluate_action(
177
+ action: :help_stranger,
178
+ description: 'Helping someone in need',
179
+ foundations: foundations
180
+ )
181
+ end
182
+
183
+ it 'calls Legion::LLM.chat with caller metadata' do
184
+ expect(Legion::LLM).to receive(:chat).with(
185
+ hash_including(caller: { extension: 'lex-agentic-social', mode: :moral_reasoning })
186
+ ).and_return(native_response)
187
+ enhancer.evaluate_action(
188
+ action: :help_stranger,
189
+ description: 'Helping someone in need',
190
+ foundations: foundations
191
+ )
192
+ end
193
+
194
+ it 'uses the content directly without calling ask' do
195
+ expect(native_response).not_to receive(:ask)
196
+ result = enhancer.evaluate_action(
197
+ action: :help_stranger,
198
+ description: 'Helping someone in need',
199
+ foundations: foundations
200
+ )
201
+ expect(result[:foundation_impacts]).to include(:care, :fairness)
202
+ end
203
+ end
153
204
  end
154
205
 
155
206
  describe '.resolve_dilemma' do
@@ -228,5 +279,58 @@ RSpec.describe Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Llm
228
279
  expect(result).to be_nil
229
280
  end
230
281
  end
282
+
283
+ context 'when LLM returns a native content response (direct dispatch path)' do
284
+ let(:native_response) do
285
+ content = <<~TEXT
286
+ CHOSEN: opt_b
287
+ CONFIDENCE: 0.75
288
+ REASONING: Native dispatch selected option b based on deontological constraints.
289
+ TEXT
290
+ double('native_response', content: content)
291
+ end
292
+
293
+ before do
294
+ allow(Legion::LLM).to receive(:chat).and_return(native_response)
295
+ end
296
+
297
+ it 'calls Legion::LLM.chat with system and user messages' do
298
+ expect(Legion::LLM).to receive(:chat).with(
299
+ hash_including(
300
+ message: array_including(
301
+ hash_including(role: 'system'),
302
+ hash_including(role: 'user')
303
+ )
304
+ )
305
+ ).and_return(native_response)
306
+ enhancer.resolve_dilemma(
307
+ dilemma_description: 'Should the agent reveal sensitive information?',
308
+ options: options,
309
+ framework: :deontological
310
+ )
311
+ end
312
+
313
+ it 'calls Legion::LLM.chat with caller metadata' do
314
+ expect(Legion::LLM).to receive(:chat).with(
315
+ hash_including(caller: { extension: 'lex-agentic-social', mode: :moral_reasoning })
316
+ ).and_return(native_response)
317
+ enhancer.resolve_dilemma(
318
+ dilemma_description: 'Should the agent reveal sensitive information?',
319
+ options: options,
320
+ framework: :deontological
321
+ )
322
+ end
323
+
324
+ it 'uses the content directly without calling ask' do
325
+ expect(native_response).not_to receive(:ask)
326
+ result = enhancer.resolve_dilemma(
327
+ dilemma_description: 'Should the agent reveal sensitive information?',
328
+ options: options,
329
+ framework: :deontological
330
+ )
331
+ expect(result[:chosen_option]).to eq('opt_b')
332
+ expect(result[:confidence]).to eq(0.75)
333
+ end
334
+ end
231
335
  end
232
336
  end
@@ -89,6 +89,25 @@ RSpec.describe Legion::Extensions::Agentic::Social::MoralReasoning::Helpers::Mor
89
89
  expect(result[:dilemma][:resolved]).to be true
90
90
  end
91
91
 
92
+ it 'uses the dilemma severity when reinforcing chosen foundations' do
93
+ dilemma_id = engine.pose_dilemma(
94
+ description: 'Low severity',
95
+ options: options,
96
+ severity: 0.2
97
+ )[:dilemma][:id]
98
+ before_weight = engine.foundation_profile[:care][:weight]
99
+
100
+ engine.resolve_dilemma(
101
+ dilemma_id: dilemma_id,
102
+ option_id: 'opt_a',
103
+ reasoning: 'Least harm',
104
+ framework: :care_ethics
105
+ )
106
+
107
+ after_weight = engine.foundation_profile[:care][:weight]
108
+ expect(after_weight - before_weight).to be_within(0.001).of(0.02)
109
+ end
110
+
92
111
  it 'returns failure for unknown dilemma_id' do
93
112
  result = engine.resolve_dilemma(
94
113
  dilemma_id: 'nonexistent', option_id: 'opt_a',
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.15
4
+ version: 0.1.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity