lex-llm-ledger 0.3.0 → 0.3.2

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: bedae52b0629ce6755fc321019b9c35917e8d4f8a304c7294956cbba864fa676
4
- data.tar.gz: c3f171de566906d37cb02c755f0336df1ce0f09dcdf769e84961f574e3b9d77e
3
+ metadata.gz: 84be271a1d8b2960d1eeef98a01c950ed2d44cab4e2e6d4c8dd6708980d027d4
4
+ data.tar.gz: 6da1a77c998891e3b2e1a5fae24b1109c51715c762b52aa308ebb73abb886188
5
5
  SHA512:
6
- metadata.gz: f8623ee921c7e0b610217925aea11e9f0f5b315b89b56d2ea239aefa9b0ac9d17b298b1f95978f784f6932ab005fa906c5c32bdd78d1f3eb47dcefb73f3762f9
7
- data.tar.gz: 278ed50a3f63394470ede944211cc0893e1c8c52bda2200209161040eba3a07a0d62aa47dbfbf37e913ecdaa8d474a4144f1961b60b3e0dcfdb3af936daf155e
6
+ metadata.gz: 51705c939e26737dfda07ac40df418887d15550e758b4db28aea1e377579b8c65108614cd5abd1ef6a309696c4b90149d1d3dacce6507011a219b90edb7d3a1e
7
+ data.tar.gz: 163077fbadc935cdeb751645fc59d06a59bf3cd47db7d93d427b721cbdc067f6749c0d6adf6641b1ded3c160c03a252f2382b383d778068b7f59ececcb1b6bb0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.2] - 2026-05-13
4
+
5
+ ### Fixed
6
+ - Keep metering-only writes from creating placeholder conversation messages so later prompt audits can attach the real user and assistant messages without sequence collisions.
7
+ - Use the request reference as the default inference metric idempotency key so metering and prompt audit events enrich the same metric row.
8
+ - Suppress duplicate insert warnings for unique races handled by the official ledger writer while retaining debug-level collision messages.
9
+
10
+ ## [0.3.1] - 2026-05-13
11
+
12
+ ### Fixed
13
+ - Recover cleanly when concurrent ledger consumers create the same conversation, request, response, metric, or identity rows.
14
+ - Keep duplicate insert recovery inside savepoints so PostgreSQL transactions remain usable after unique constraint races.
15
+ - Remove temporary prompt runner debug output while preserving single-message subscription prefetch behavior.
16
+
3
17
  ## [0.3.0] - 2026-05-08
4
18
 
5
19
  ### Changed
@@ -11,6 +11,8 @@ module Legion
11
11
  class Metering < Legion::Extensions::Actors::Subscription
12
12
  include Helpers::SubscriptionActor
13
13
 
14
+ prefetch 1
15
+
14
16
  def runner_class = Legion::Extensions::Llm::Ledger::Runners::Metering
15
17
 
16
18
  def runner_function
@@ -11,6 +11,8 @@ module Legion
11
11
  class Prompts < Legion::Extensions::Actors::Subscription
12
12
  include Helpers::SubscriptionActor
13
13
 
14
+ prefetch 1
15
+
14
16
  def runner_class = Legion::Extensions::Llm::Ledger::Runners::Prompts
15
17
 
16
18
  def runner_function
@@ -11,6 +11,8 @@ module Legion
11
11
  class Tools < Legion::Extensions::Actors::Subscription
12
12
  include Helpers::SubscriptionActor
13
13
 
14
+ prefetch 1
15
+
14
16
  def runner_class = Legion::Extensions::Llm::Ledger::Runners::Tools
15
17
 
16
18
  def runner_function
@@ -184,7 +184,7 @@ module Legion
184
184
  end
185
185
 
186
186
  def official_metric_uuid(payload)
187
- ref = payload[:message_id] || "metric:#{Writers::OfficialRecordWriter.request_ref(payload)}"
187
+ ref = payload[:metric_id] || payload[:metric_ref] || "metric:#{Writers::OfficialRecordWriter.request_ref(payload)}"
188
188
  Writers::OfficialRecordWriter.stable_uuid(ref)
189
189
  end
190
190
 
@@ -19,13 +19,15 @@ module Legion
19
19
 
20
20
  module_function
21
21
 
22
- def insert_row(db, table, attributes, operation:)
22
+ def insert_row(db, table, attributes, operation:, warn_on_unique: true)
23
23
  row_id = db[table].insert(attributes)
24
24
  log.info(log_message('inserted', table, operation, row_id, attributes))
25
25
  row_id
26
26
  rescue Sequel::UniqueConstraintViolation => e
27
- log.warn(log_message('insert_failed', table, operation, nil, attributes,
28
- error_class: e.class, error: e.message))
27
+ if warn_on_unique
28
+ log.warn(log_message('insert_failed', table, operation, nil, attributes,
29
+ error_class: e.class, error: e.message))
30
+ end
29
31
  raise
30
32
  rescue StandardError => e
31
33
  log.error(log_message('insert_failed', table, operation, nil, attributes,
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Ledger
7
- VERSION = '0.3.0'
7
+ VERSION = '0.3.2'
8
8
  end
9
9
  end
10
10
  end
@@ -42,8 +42,7 @@ module Legion
42
42
 
43
43
  db.transaction do
44
44
  conversation = find_or_create_conversation(db, body)
45
- user_message = find_or_create_user_message(db, conversation, body)
46
- request = find_or_create_request(db, conversation, user_message, body)
45
+ request = find_or_create_request(db, conversation, nil, body)
47
46
  response = find_or_create_response(db, request, nil, body)
48
47
  metric = find_or_create_metric(db, request, response, body)
49
48
  result = { result: :ok, request_id: request[:id], response_id: response[:id], metric_id: metric[:id] }
@@ -57,20 +56,26 @@ module Legion
57
56
  existing = db[:llm_conversations].where(uuid: uuid).first
58
57
  return existing if existing
59
58
 
60
- id = insert_row(db, :llm_conversations, {
61
- uuid: uuid,
62
- title: body[:title] || body[:conversation_title],
63
- classification_level: classification_level(body),
64
- contains_phi: contains_phi?(body),
65
- contains_pii: contains_pii?(body),
66
- retention_policy: body[:retention_policy] || 'default',
67
- expires_at: body[:expires_at],
68
- recorded_at: recorded_at(body),
69
- inserted_at: Time.now.utc,
70
- created_at: Time.now.utc,
71
- updated_at: Time.now.utc
72
- }, operation: 'official_record_writer.conversation')
59
+ id = insert_with_savepoint(db, :llm_conversations, {
60
+ uuid: uuid,
61
+ title: body[:title] || body[:conversation_title],
62
+ classification_level: classification_level(body),
63
+ contains_phi: contains_phi?(body),
64
+ contains_pii: contains_pii?(body),
65
+ retention_policy: body[:retention_policy] || 'default',
66
+ expires_at: body[:expires_at],
67
+ recorded_at: recorded_at(body),
68
+ inserted_at: Time.now.utc,
69
+ created_at: Time.now.utc,
70
+ updated_at: Time.now.utc
71
+ }, operation: 'official_record_writer.conversation')
73
72
  db[:llm_conversations][id: id]
73
+ rescue Sequel::UniqueConstraintViolation => e
74
+ log.debug("[ledger] conversation collision resolved uuid=#{uuid} error=#{e.class}")
75
+ existing = db[:llm_conversations].where(uuid: uuid).first
76
+ return existing if existing
77
+
78
+ raise
74
79
  end
75
80
 
76
81
  def find_or_create_user_message(db, conversation, body)
@@ -80,20 +85,18 @@ module Legion
80
85
 
81
86
  seq = body[:message_seq] ? integer(body[:message_seq]) : next_message_seq(db, conversation)
82
87
  begin
83
- id = db.transaction(savepoint: true) do
84
- insert_row(db, :llm_messages, {
85
- uuid: uuid,
86
- conversation_id: conversation[:id],
87
- seq: seq,
88
- role: 'user',
89
- content_type: 'text',
90
- content: request_content(body),
91
- input_tokens: tokens(body)[:input_tokens],
92
- output_tokens: 0,
93
- created_at: recorded_at(body),
94
- inserted_at: Time.now.utc
95
- }, operation: 'official_record_writer.user_message')
96
- end
88
+ id = insert_with_savepoint(db, :llm_messages, {
89
+ uuid: uuid,
90
+ conversation_id: conversation[:id],
91
+ seq: seq,
92
+ role: 'user',
93
+ content_type: 'text',
94
+ content: request_content(body),
95
+ input_tokens: tokens(body)[:input_tokens],
96
+ output_tokens: 0,
97
+ created_at: recorded_at(body),
98
+ inserted_at: Time.now.utc
99
+ }, operation: 'official_record_writer.user_message')
97
100
  db[:llm_messages][id: id]
98
101
  rescue Sequel::UniqueConstraintViolation => e
99
102
  log.debug("[ledger] seq collision resolved uuid=#{uuid} conversation_id=#{conversation[:id]} error=#{e.class}")
@@ -105,38 +108,41 @@ module Legion
105
108
  def find_or_create_request(db, conversation, latest_message, body)
106
109
  request_id = request_ref(body)
107
110
  existing = db[:llm_message_inference_requests].where(request_ref: request_id).first
108
- if existing
109
- enrich_request!(db, existing, body)
110
- return existing
111
- end
111
+ return enrich_request!(db, existing, body, latest_message) if existing
112
112
 
113
113
  operation = operation(body)
114
114
  caller_refs = caller_identity_refs(db, body)
115
- id = insert_row(db, :llm_message_inference_requests, {
116
- uuid: stable_uuid(request_id),
117
- conversation_id: conversation[:id],
118
- latest_message_id: latest_message[:id],
119
- caller_principal_id: caller_refs[:principal_id],
120
- caller_identity_id: caller_refs[:identity_id],
121
- runtime_caller_type: caller_type(body),
122
- request_ref: request_id,
123
- correlation_ref: correlation_id(body),
124
- correlation_id: correlation_id(body),
125
- exchange_ref: body[:exchange_id],
126
- request_type: operation,
127
- operation: operation,
128
- idempotency_key: body[:idempotency_key] || request_id,
129
- status: 'responded',
130
- context_message_count: Array(body.dig(:request, :messages) || body[:messages]).size,
131
- request_capture_mode: 'full',
132
- request_json: json_dump(request_payload(body)),
133
- classification_level: classification_level(body),
134
- cost_center: billing(body)[:cost_center],
135
- budget_key: billing(body)[:budget_id] || billing(body)[:budget_key],
136
- requested_at: recorded_at(body),
137
- inserted_at: Time.now.utc
138
- }, operation: 'official_record_writer.inference_request')
115
+ id = insert_with_savepoint(db, :llm_message_inference_requests, {
116
+ uuid: stable_uuid(request_id),
117
+ conversation_id: conversation[:id],
118
+ latest_message_id: latest_message&.dig(:id),
119
+ caller_principal_id: caller_refs[:principal_id],
120
+ caller_identity_id: caller_refs[:identity_id],
121
+ runtime_caller_type: caller_type(body),
122
+ request_ref: request_id,
123
+ correlation_ref: correlation_id(body),
124
+ correlation_id: correlation_id(body),
125
+ exchange_ref: body[:exchange_id],
126
+ request_type: operation,
127
+ operation: operation,
128
+ idempotency_key: body[:idempotency_key] || request_id,
129
+ status: 'responded',
130
+ context_message_count: Array(body.dig(:request, :messages) || body[:messages]).size,
131
+ request_capture_mode: 'full',
132
+ request_json: json_dump(request_payload(body)),
133
+ classification_level: classification_level(body),
134
+ cost_center: billing(body)[:cost_center],
135
+ budget_key: billing(body)[:budget_id] || billing(body)[:budget_key],
136
+ requested_at: recorded_at(body),
137
+ inserted_at: Time.now.utc
138
+ }, operation: 'official_record_writer.inference_request')
139
139
  db[:llm_message_inference_requests][id: id]
140
+ rescue Sequel::UniqueConstraintViolation => e
141
+ log.debug("[ledger] request collision resolved request_ref=#{request_id} error=#{e.class}")
142
+ existing = db[:llm_message_inference_requests].where(request_ref: request_id).first
143
+ return enrich_request!(db, existing, body, latest_message) if existing
144
+
145
+ raise
140
146
  end
141
147
 
142
148
  def find_or_create_response_message(db, conversation, request, body)
@@ -147,22 +153,20 @@ module Legion
147
153
  latest = db[:llm_messages][id: request[:latest_message_id]]
148
154
  seq = (latest&.dig(:seq) || 1) + 1
149
155
  begin
150
- id = db.transaction(savepoint: true) do
151
- insert_row(db, :llm_messages, {
152
- uuid: uuid,
153
- conversation_id: conversation[:id],
154
- parent_message_id: latest&.dig(:id),
155
- message_inference_request_id: request[:id],
156
- seq: seq,
157
- role: 'assistant',
158
- content_type: 'text',
159
- content: response_content(body),
160
- input_tokens: 0,
161
- output_tokens: tokens(body)[:output_tokens],
162
- created_at: recorded_at(body),
163
- inserted_at: Time.now.utc
164
- }, operation: 'official_record_writer.response_message')
165
- end
156
+ id = insert_with_savepoint(db, :llm_messages, {
157
+ uuid: uuid,
158
+ conversation_id: conversation[:id],
159
+ parent_message_id: latest&.dig(:id),
160
+ message_inference_request_id: request[:id],
161
+ seq: seq,
162
+ role: 'assistant',
163
+ content_type: 'text',
164
+ content: response_content(body),
165
+ input_tokens: 0,
166
+ output_tokens: tokens(body)[:output_tokens],
167
+ created_at: recorded_at(body),
168
+ inserted_at: Time.now.utc
169
+ }, operation: 'official_record_writer.response_message')
166
170
  db[:llm_messages][id: id]
167
171
  rescue Sequel::UniqueConstraintViolation => e
168
172
  log.debug("[ledger] seq collision resolved uuid=#{uuid} conversation_id=#{conversation[:id]} error=#{e.class}")
@@ -179,28 +183,37 @@ module Legion
179
183
  return existing
180
184
  end
181
185
 
182
- id = insert_row(db, :llm_message_inference_responses, {
183
- uuid: response_uuid,
184
- message_inference_request_id: request[:id],
185
- response_message_id: response_message&.dig(:id),
186
- provider: provider(body),
187
- provider_instance: provider_instance(body),
188
- model_key: model_id(body),
189
- tier: tier(body),
190
- runner_ref: body[:worker_id] || body[:runner_ref],
191
- provider_response_ref: body[:provider_response_ref],
192
- status: body[:error] ? 'error' : 'success',
193
- finish_reason: finish_reason(body),
194
- latency_ms: integer(body[:latency_ms]),
195
- wall_clock_ms: integer(body[:wall_clock_ms]),
196
- response_capture_mode: 'full',
197
- response_json: json_dump(visible_response(body)),
198
- response_thinking_json: json_dump(thinking_response(body)),
199
- dispatch_path: body[:dispatch_path] || body[:tier],
200
- responded_at: recorded_at(body),
201
- inserted_at: Time.now.utc
202
- }, operation: 'official_record_writer.inference_response')
186
+ id = insert_with_savepoint(db, :llm_message_inference_responses, {
187
+ uuid: response_uuid,
188
+ message_inference_request_id: request[:id],
189
+ response_message_id: response_message&.dig(:id),
190
+ provider: provider(body),
191
+ provider_instance: provider_instance(body),
192
+ model_key: model_id(body),
193
+ tier: tier(body),
194
+ runner_ref: body[:worker_id] || body[:runner_ref],
195
+ provider_response_ref: body[:provider_response_ref],
196
+ status: body[:error] ? 'error' : 'success',
197
+ finish_reason: finish_reason(body),
198
+ latency_ms: integer(body[:latency_ms]),
199
+ wall_clock_ms: integer(body[:wall_clock_ms]),
200
+ response_capture_mode: 'full',
201
+ response_json: json_dump(visible_response(body)),
202
+ response_thinking_json: json_dump(thinking_response(body)),
203
+ dispatch_path: body[:dispatch_path] || body[:tier],
204
+ responded_at: recorded_at(body),
205
+ inserted_at: Time.now.utc
206
+ }, operation: 'official_record_writer.inference_response')
203
207
  db[:llm_message_inference_responses][id: id]
208
+ rescue Sequel::UniqueConstraintViolation => e
209
+ log.debug("[ledger] response collision resolved uuid=#{response_uuid} error=#{e.class}")
210
+ existing = db[:llm_message_inference_responses].where(uuid: response_uuid).first
211
+ if existing
212
+ enrich_response!(db, existing, response_message, body)
213
+ return existing
214
+ end
215
+
216
+ raise
204
217
  end
205
218
 
206
219
  def enrich_response!(db, existing, response_message, body)
@@ -232,36 +245,48 @@ module Legion
232
245
  end
233
246
 
234
247
  def find_or_create_metric(db, request, response, body)
235
- metric_uuid = stable_uuid(reference(body, :message_id) || "metric:#{request_ref(body)}")
248
+ metric_uuid = stable_uuid(reference(body, :metric_id, :metric_ref) || "metric:#{request_ref(body)}")
236
249
  existing = db[:llm_message_inference_metrics].where(uuid: metric_uuid).first
237
250
  return existing if existing
238
251
 
239
252
  token_values = tokens(body)
240
- id = insert_row(db, :llm_message_inference_metrics, {
241
- uuid: metric_uuid,
242
- message_inference_request_id: request[:id],
243
- message_inference_response_id: response[:id],
244
- provider: provider(body),
245
- model_key: model_id(body),
246
- tier: tier(body),
247
- input_tokens: token_values[:input_tokens],
248
- output_tokens: token_values[:output_tokens],
249
- thinking_tokens: token_values[:thinking_tokens],
250
- total_tokens: token_values[:total_tokens],
251
- latency_ms: integer(body[:latency_ms]),
252
- wall_clock_ms: integer(body[:wall_clock_ms]),
253
- cost_usd: cost_usd(body),
254
- currency: body[:currency] || 'USD',
255
- cost_center: billing(body)[:cost_center],
256
- budget_key: billing(body)[:budget_id] || billing(body)[:budget_key],
257
- recorded_at: recorded_at(body),
258
- inserted_at: Time.now.utc
259
- }, operation: 'official_record_writer.inference_metric')
253
+ id = insert_with_savepoint(db, :llm_message_inference_metrics, {
254
+ uuid: metric_uuid,
255
+ message_inference_request_id: request[:id],
256
+ message_inference_response_id: response[:id],
257
+ provider: provider(body),
258
+ model_key: model_id(body),
259
+ tier: tier(body),
260
+ input_tokens: token_values[:input_tokens],
261
+ output_tokens: token_values[:output_tokens],
262
+ thinking_tokens: token_values[:thinking_tokens],
263
+ total_tokens: token_values[:total_tokens],
264
+ latency_ms: integer(body[:latency_ms]),
265
+ wall_clock_ms: integer(body[:wall_clock_ms]),
266
+ cost_usd: cost_usd(body),
267
+ currency: body[:currency] || 'USD',
268
+ cost_center: billing(body)[:cost_center],
269
+ budget_key: billing(body)[:budget_id] || billing(body)[:budget_key],
270
+ recorded_at: recorded_at(body),
271
+ inserted_at: Time.now.utc
272
+ }, operation: 'official_record_writer.inference_metric')
260
273
  db[:llm_message_inference_metrics][id: id]
274
+ rescue Sequel::UniqueConstraintViolation => e
275
+ log.debug("[ledger] metric collision resolved uuid=#{metric_uuid} error=#{e.class}")
276
+ existing = db[:llm_message_inference_metrics].where(uuid: metric_uuid).first
277
+ return existing if existing
278
+
279
+ raise
261
280
  end
262
281
 
263
282
  def insert_row(db, table, attributes, operation:)
264
- Helpers::PersistenceLogging.insert_row(db, table, attributes, operation: operation)
283
+ Helpers::PersistenceLogging.insert_row(db, table, attributes, operation: operation, warn_on_unique: false)
284
+ end
285
+
286
+ def insert_with_savepoint(db, table, attributes, operation:)
287
+ db.transaction(savepoint: true) do
288
+ insert_row(db, table, attributes, operation: operation)
289
+ end
265
290
  end
266
291
 
267
292
  def request_ref(body)
@@ -277,8 +302,9 @@ module Legion
277
302
  db[:llm_messages].where(id: response_message[:id]).update(message_inference_response_id: response[:id])
278
303
  end
279
304
 
280
- def enrich_request!(db, existing, body)
305
+ def enrich_request!(db, existing, body, latest_message = nil)
281
306
  updates = {}
307
+ update_if_missing(updates, existing, :latest_message_id, latest_message&.dig(:id))
282
308
  caller_refs = caller_identity_refs(db, body)
283
309
  updates[:caller_identity_id] = caller_refs[:identity_id] if existing[:caller_identity_id].nil? && caller_refs[:identity_id]
284
310
  updates[:caller_principal_id] = caller_refs[:principal_id] if existing[:caller_principal_id].nil? && caller_refs[:principal_id]
@@ -290,10 +316,11 @@ module Legion
290
316
  msg_count = Array(body.dig(:request, :messages) || body[:messages]).size
291
317
  updates[:context_message_count] = msg_count if existing[:context_message_count].to_i.zero? && msg_count.positive?
292
318
 
293
- return if updates.empty?
319
+ return existing if updates.empty?
294
320
 
295
321
  db[:llm_message_inference_requests].where(id: existing[:id]).update(updates)
296
322
  log.info("[ledger] enriched request id=#{existing[:id]} fields=#{updates.keys.join(',')}")
323
+ existing.merge(updates)
297
324
  end
298
325
 
299
326
  def caller_identity(body)
@@ -396,19 +423,22 @@ module Legion
396
423
  existing = table.where(name: provider_name).first
397
424
  return existing if existing
398
425
 
399
- id = insert_row(db, :identity_providers, {
400
- uuid: deterministic_uuid("identity_provider:#{provider_name}"),
401
- name: provider_name,
402
- provider_type: provider_name == 'local' ? 'local' : 'external',
403
- facing: 'internal',
404
- source: 'ledger',
405
- created_at: Time.now.utc,
406
- updated_at: Time.now.utc
407
- }, operation: 'official_record_writer.identity_provider')
426
+ id = insert_with_savepoint(db, :identity_providers, {
427
+ uuid: deterministic_uuid("identity_provider:#{provider_name}"),
428
+ name: provider_name,
429
+ provider_type: provider_name == 'local' ? 'local' : 'external',
430
+ facing: 'internal',
431
+ source: 'ledger',
432
+ created_at: Time.now.utc,
433
+ updated_at: Time.now.utc
434
+ }, operation: 'official_record_writer.identity_provider')
408
435
  table[id: id]
409
436
  rescue Sequel::UniqueConstraintViolation => e
410
437
  handle_exception(e, level: :debug, handled: true, operation: 'official_record_writer.identity_provider_race')
411
- table.where(name: provider_name).first
438
+ existing = table.where(name: provider_name).first
439
+ return existing if existing
440
+
441
+ raise
412
442
  end
413
443
 
414
444
  def find_or_create_identity_principal(db, descriptor)
@@ -416,19 +446,22 @@ module Legion
416
446
  existing = table.where(canonical_name: descriptor[:canonical_name], kind: descriptor[:kind]).first
417
447
  return existing if existing
418
448
 
419
- id = insert_row(db, :identity_principals, {
420
- uuid: deterministic_uuid("identity_principal:#{descriptor[:kind]}:#{descriptor[:canonical_name]}"),
421
- canonical_name: descriptor[:canonical_name],
422
- kind: descriptor[:kind],
423
- display_name: descriptor[:canonical_name],
424
- last_seen_at: Time.now.utc,
425
- created_at: Time.now.utc,
426
- updated_at: Time.now.utc
427
- }, operation: 'official_record_writer.identity_principal')
449
+ id = insert_with_savepoint(db, :identity_principals, {
450
+ uuid: deterministic_uuid("identity_principal:#{descriptor[:kind]}:#{descriptor[:canonical_name]}"),
451
+ canonical_name: descriptor[:canonical_name],
452
+ kind: descriptor[:kind],
453
+ display_name: descriptor[:canonical_name],
454
+ last_seen_at: Time.now.utc,
455
+ created_at: Time.now.utc,
456
+ updated_at: Time.now.utc
457
+ }, operation: 'official_record_writer.identity_principal')
428
458
  table[id: id]
429
459
  rescue Sequel::UniqueConstraintViolation => e
430
460
  handle_exception(e, level: :debug, handled: true, operation: 'official_record_writer.identity_principal_race')
431
- table.where(canonical_name: descriptor[:canonical_name], kind: descriptor[:kind]).first
461
+ existing = table.where(canonical_name: descriptor[:canonical_name], kind: descriptor[:kind]).first
462
+ return existing if existing
463
+
464
+ raise
432
465
  end
433
466
 
434
467
  def find_or_create_identity(db, principal, provider, descriptor)
@@ -441,22 +474,25 @@ module Legion
441
474
  return existing if existing
442
475
 
443
476
  uuid_key = "identity:#{principal[:id]}:#{provider[:id]}:#{descriptor[:provider_identity_key]}"
444
- id = insert_row(db, :identities, {
445
- uuid: deterministic_uuid(uuid_key),
446
- principal_id: principal[:id],
447
- provider_id: provider[:id],
448
- provider_identity_key: descriptor[:provider_identity_key],
449
- last_authenticated_at: Time.now.utc,
450
- account_type: 'primary',
451
- is_default: true,
452
- created_at: Time.now.utc,
453
- updated_at: Time.now.utc
454
- }, operation: 'official_record_writer.identity')
477
+ id = insert_with_savepoint(db, :identities, {
478
+ uuid: deterministic_uuid(uuid_key),
479
+ principal_id: principal[:id],
480
+ provider_id: provider[:id],
481
+ provider_identity_key: descriptor[:provider_identity_key],
482
+ last_authenticated_at: Time.now.utc,
483
+ account_type: 'primary',
484
+ is_default: true,
485
+ created_at: Time.now.utc,
486
+ updated_at: Time.now.utc
487
+ }, operation: 'official_record_writer.identity')
455
488
  table[id: id]
456
489
  rescue Sequel::UniqueConstraintViolation => e
457
490
  handle_exception(e, level: :debug, handled: true, operation: 'official_record_writer.identity_race')
458
- table.where(principal_id: principal[:id], provider_id: provider[:id],
459
- provider_identity_key: descriptor[:provider_identity_key]).first
491
+ existing = table.where(principal_id: principal[:id], provider_id: provider[:id],
492
+ provider_identity_key: descriptor[:provider_identity_key]).first
493
+ return existing if existing
494
+
495
+ raise
460
496
  end
461
497
 
462
498
  def identity_tables_available?(db)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-llm-ledger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity