lex-llm-ledger 0.2.9 → 0.3.1

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: 6719c8b6ccdca27627040432c378248b93a95e95fd5deb4873262ed1ba287655
4
- data.tar.gz: fb09090ebb8a2ec6aef542f03fbd804e11bcc9c33a9b97d5e4004cded0a11ba3
3
+ metadata.gz: a69d4ccf5975f45c795e6e54ab6ed59272a269855394bbef4d1149a48bd338e7
4
+ data.tar.gz: 60bfc3f268526d0cf427ccfa90f2e230f9e0491fd88584ff685604c93faf63c4
5
5
  SHA512:
6
- metadata.gz: 43f307490c3f943e932eea1abf2584cfd115d472de59b12c56acb6201f295890b9d42d34c1c56d0394a604d432c18b1bb2bc80b27396d060e50e386dc858b1fd
7
- data.tar.gz: b452b9ef5639bb01c7c1ccb311a97ed29fad412efa4bb14a0a05cdcaf6dac6eea3fd728679709f8f05f6030d7a9e0cdddeb4b359a6bf36b5ebf8d391d2228605
6
+ metadata.gz: e166ea0dc31b758dd45d5c1595f6a72c78629b7dfd69d09d9928a699ce14f475658d9bf77f08018608bc98f394532f4c19b5887c3b2649bb1761aecbe2b4b5c8
7
+ data.tar.gz: 226be9d0592d67fcca0ff956fe0c62db56a311cc3f6bde5a16523f21801d43079d976a6d6582e2a0f1022396089555495df37ccf81ccfd56ddfd3f2344f9d27f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1] - 2026-05-13
4
+
5
+ ### Fixed
6
+ - Recover cleanly when concurrent ledger consumers create the same conversation, request, response, metric, or identity rows.
7
+ - Keep duplicate insert recovery inside savepoints so PostgreSQL transactions remain usable after unique constraint races.
8
+ - Remove temporary prompt runner debug output while preserving single-message subscription prefetch behavior.
9
+
10
+ ## [0.3.0] - 2026-05-08
11
+
12
+ ### Changed
13
+ - Renamed all `portable_identity_*` table references to canonical identity table names (`identities`, `identity_principals`, `identity_providers`).
14
+ - Renamed internal methods: `resolve_portable_identity` → `resolve_identity`, `find_or_create_portable_identity` → `find_or_create_identity`.
15
+
3
16
  ## [0.2.9] - 2026-05-07
4
17
 
5
18
  ### Fixed
@@ -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
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Llm
6
6
  module Ledger
7
- VERSION = '0.2.9'
7
+ VERSION = '0.3.1'
8
8
  end
9
9
  end
10
10
  end
@@ -57,20 +57,26 @@ module Legion
57
57
  existing = db[:llm_conversations].where(uuid: uuid).first
58
58
  return existing if existing
59
59
 
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')
60
+ id = insert_with_savepoint(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')
73
73
  db[:llm_conversations][id: id]
74
+ rescue Sequel::UniqueConstraintViolation => e
75
+ log.debug("[ledger] conversation collision resolved uuid=#{uuid} error=#{e.class}")
76
+ existing = db[:llm_conversations].where(uuid: uuid).first
77
+ return existing if existing
78
+
79
+ raise
74
80
  end
75
81
 
76
82
  def find_or_create_user_message(db, conversation, body)
@@ -80,20 +86,18 @@ module Legion
80
86
 
81
87
  seq = body[:message_seq] ? integer(body[:message_seq]) : next_message_seq(db, conversation)
82
88
  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
89
+ id = insert_with_savepoint(db, :llm_messages, {
90
+ uuid: uuid,
91
+ conversation_id: conversation[:id],
92
+ seq: seq,
93
+ role: 'user',
94
+ content_type: 'text',
95
+ content: request_content(body),
96
+ input_tokens: tokens(body)[:input_tokens],
97
+ output_tokens: 0,
98
+ created_at: recorded_at(body),
99
+ inserted_at: Time.now.utc
100
+ }, operation: 'official_record_writer.user_message')
97
101
  db[:llm_messages][id: id]
98
102
  rescue Sequel::UniqueConstraintViolation => e
99
103
  log.debug("[ledger] seq collision resolved uuid=#{uuid} conversation_id=#{conversation[:id]} error=#{e.class}")
@@ -112,31 +116,40 @@ module Legion
112
116
 
113
117
  operation = operation(body)
114
118
  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')
119
+ id = insert_with_savepoint(db, :llm_message_inference_requests, {
120
+ uuid: stable_uuid(request_id),
121
+ conversation_id: conversation[:id],
122
+ latest_message_id: latest_message[:id],
123
+ caller_principal_id: caller_refs[:principal_id],
124
+ caller_identity_id: caller_refs[:identity_id],
125
+ runtime_caller_type: caller_type(body),
126
+ request_ref: request_id,
127
+ correlation_ref: correlation_id(body),
128
+ correlation_id: correlation_id(body),
129
+ exchange_ref: body[:exchange_id],
130
+ request_type: operation,
131
+ operation: operation,
132
+ idempotency_key: body[:idempotency_key] || request_id,
133
+ status: 'responded',
134
+ context_message_count: Array(body.dig(:request, :messages) || body[:messages]).size,
135
+ request_capture_mode: 'full',
136
+ request_json: json_dump(request_payload(body)),
137
+ classification_level: classification_level(body),
138
+ cost_center: billing(body)[:cost_center],
139
+ budget_key: billing(body)[:budget_id] || billing(body)[:budget_key],
140
+ requested_at: recorded_at(body),
141
+ inserted_at: Time.now.utc
142
+ }, operation: 'official_record_writer.inference_request')
139
143
  db[:llm_message_inference_requests][id: id]
144
+ rescue Sequel::UniqueConstraintViolation => e
145
+ log.debug("[ledger] request collision resolved request_ref=#{request_id} error=#{e.class}")
146
+ existing = db[:llm_message_inference_requests].where(request_ref: request_id).first
147
+ if existing
148
+ enrich_request!(db, existing, body)
149
+ return existing
150
+ end
151
+
152
+ raise
140
153
  end
141
154
 
142
155
  def find_or_create_response_message(db, conversation, request, body)
@@ -147,22 +160,20 @@ module Legion
147
160
  latest = db[:llm_messages][id: request[:latest_message_id]]
148
161
  seq = (latest&.dig(:seq) || 1) + 1
149
162
  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
163
+ id = insert_with_savepoint(db, :llm_messages, {
164
+ uuid: uuid,
165
+ conversation_id: conversation[:id],
166
+ parent_message_id: latest&.dig(:id),
167
+ message_inference_request_id: request[:id],
168
+ seq: seq,
169
+ role: 'assistant',
170
+ content_type: 'text',
171
+ content: response_content(body),
172
+ input_tokens: 0,
173
+ output_tokens: tokens(body)[:output_tokens],
174
+ created_at: recorded_at(body),
175
+ inserted_at: Time.now.utc
176
+ }, operation: 'official_record_writer.response_message')
166
177
  db[:llm_messages][id: id]
167
178
  rescue Sequel::UniqueConstraintViolation => e
168
179
  log.debug("[ledger] seq collision resolved uuid=#{uuid} conversation_id=#{conversation[:id]} error=#{e.class}")
@@ -179,28 +190,37 @@ module Legion
179
190
  return existing
180
191
  end
181
192
 
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')
193
+ id = insert_with_savepoint(db, :llm_message_inference_responses, {
194
+ uuid: response_uuid,
195
+ message_inference_request_id: request[:id],
196
+ response_message_id: response_message&.dig(:id),
197
+ provider: provider(body),
198
+ provider_instance: provider_instance(body),
199
+ model_key: model_id(body),
200
+ tier: tier(body),
201
+ runner_ref: body[:worker_id] || body[:runner_ref],
202
+ provider_response_ref: body[:provider_response_ref],
203
+ status: body[:error] ? 'error' : 'success',
204
+ finish_reason: finish_reason(body),
205
+ latency_ms: integer(body[:latency_ms]),
206
+ wall_clock_ms: integer(body[:wall_clock_ms]),
207
+ response_capture_mode: 'full',
208
+ response_json: json_dump(visible_response(body)),
209
+ response_thinking_json: json_dump(thinking_response(body)),
210
+ dispatch_path: body[:dispatch_path] || body[:tier],
211
+ responded_at: recorded_at(body),
212
+ inserted_at: Time.now.utc
213
+ }, operation: 'official_record_writer.inference_response')
203
214
  db[:llm_message_inference_responses][id: id]
215
+ rescue Sequel::UniqueConstraintViolation => e
216
+ log.debug("[ledger] response collision resolved uuid=#{response_uuid} error=#{e.class}")
217
+ existing = db[:llm_message_inference_responses].where(uuid: response_uuid).first
218
+ if existing
219
+ enrich_response!(db, existing, response_message, body)
220
+ return existing
221
+ end
222
+
223
+ raise
204
224
  end
205
225
 
206
226
  def enrich_response!(db, existing, response_message, body)
@@ -237,33 +257,45 @@ module Legion
237
257
  return existing if existing
238
258
 
239
259
  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')
260
+ id = insert_with_savepoint(db, :llm_message_inference_metrics, {
261
+ uuid: metric_uuid,
262
+ message_inference_request_id: request[:id],
263
+ message_inference_response_id: response[:id],
264
+ provider: provider(body),
265
+ model_key: model_id(body),
266
+ tier: tier(body),
267
+ input_tokens: token_values[:input_tokens],
268
+ output_tokens: token_values[:output_tokens],
269
+ thinking_tokens: token_values[:thinking_tokens],
270
+ total_tokens: token_values[:total_tokens],
271
+ latency_ms: integer(body[:latency_ms]),
272
+ wall_clock_ms: integer(body[:wall_clock_ms]),
273
+ cost_usd: cost_usd(body),
274
+ currency: body[:currency] || 'USD',
275
+ cost_center: billing(body)[:cost_center],
276
+ budget_key: billing(body)[:budget_id] || billing(body)[:budget_key],
277
+ recorded_at: recorded_at(body),
278
+ inserted_at: Time.now.utc
279
+ }, operation: 'official_record_writer.inference_metric')
260
280
  db[:llm_message_inference_metrics][id: id]
281
+ rescue Sequel::UniqueConstraintViolation => e
282
+ log.debug("[ledger] metric collision resolved uuid=#{metric_uuid} error=#{e.class}")
283
+ existing = db[:llm_message_inference_metrics].where(uuid: metric_uuid).first
284
+ return existing if existing
285
+
286
+ raise
261
287
  end
262
288
 
263
289
  def insert_row(db, table, attributes, operation:)
264
290
  Helpers::PersistenceLogging.insert_row(db, table, attributes, operation: operation)
265
291
  end
266
292
 
293
+ def insert_with_savepoint(db, table, attributes, operation:)
294
+ db.transaction(savepoint: true) do
295
+ insert_row(db, table, attributes, operation: operation)
296
+ end
297
+ end
298
+
267
299
  def request_ref(body)
268
300
  body[:__ledger_request_ref] ||= reference(body, :request_id, :request_ref) ||
269
301
  correlation_id(body) ||
@@ -322,11 +354,11 @@ module Legion
322
354
  refs = { principal_id: explicit_principal_id, identity_id: explicit_identity_id }.compact
323
355
  unless refs[:principal_id] && refs[:identity_id]
324
356
  if explicit_identity_id && !explicit_principal_id && identity_tables_available?(db)
325
- row = db[:portable_identities].where(id: explicit_identity_id).first
357
+ row = db[:identities].where(id: explicit_identity_id).first
326
358
  refs[:principal_id] = row[:principal_id] if row
327
359
  end
328
360
 
329
- resolved = resolve_portable_identity(db, body)
361
+ resolved = resolve_identity(db, body)
330
362
  refs[:principal_id] ||= resolved[:principal_id]
331
363
  refs[:identity_id] ||= resolved[:identity_id]
332
364
  end
@@ -334,7 +366,7 @@ module Legion
334
366
  end
335
367
  end
336
368
 
337
- def resolve_portable_identity(db, body)
369
+ def resolve_identity(db, body)
338
370
  return {} unless identity_tables_available?(db)
339
371
 
340
372
  descriptor = parsed_identity_descriptor(body)
@@ -342,7 +374,7 @@ module Legion
342
374
 
343
375
  provider = find_or_create_identity_provider(db, descriptor[:provider_name])
344
376
  principal = find_or_create_identity_principal(db, descriptor)
345
- identity = find_or_create_portable_identity(db, principal, provider, descriptor)
377
+ identity = find_or_create_identity(db, principal, provider, descriptor)
346
378
 
347
379
  { principal_id: principal[:id], identity_id: identity[:id] }
348
380
  rescue StandardError => e
@@ -392,47 +424,53 @@ module Legion
392
424
  end
393
425
 
394
426
  def find_or_create_identity_provider(db, provider_name)
395
- table = db[:portable_identity_providers]
427
+ table = db[:identity_providers]
396
428
  existing = table.where(name: provider_name).first
397
429
  return existing if existing
398
430
 
399
- id = insert_row(db, :portable_identity_providers, {
400
- uuid: deterministic_uuid("portable_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')
431
+ id = insert_with_savepoint(db, :identity_providers, {
432
+ uuid: deterministic_uuid("identity_provider:#{provider_name}"),
433
+ name: provider_name,
434
+ provider_type: provider_name == 'local' ? 'local' : 'external',
435
+ facing: 'internal',
436
+ source: 'ledger',
437
+ created_at: Time.now.utc,
438
+ updated_at: Time.now.utc
439
+ }, operation: 'official_record_writer.identity_provider')
408
440
  table[id: id]
409
441
  rescue Sequel::UniqueConstraintViolation => e
410
442
  handle_exception(e, level: :debug, handled: true, operation: 'official_record_writer.identity_provider_race')
411
- table.where(name: provider_name).first
443
+ existing = table.where(name: provider_name).first
444
+ return existing if existing
445
+
446
+ raise
412
447
  end
413
448
 
414
449
  def find_or_create_identity_principal(db, descriptor)
415
- table = db[:portable_identity_principals]
450
+ table = db[:identity_principals]
416
451
  existing = table.where(canonical_name: descriptor[:canonical_name], kind: descriptor[:kind]).first
417
452
  return existing if existing
418
453
 
419
- id = insert_row(db, :portable_identity_principals, {
420
- uuid: deterministic_uuid("portable_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')
454
+ id = insert_with_savepoint(db, :identity_principals, {
455
+ uuid: deterministic_uuid("identity_principal:#{descriptor[:kind]}:#{descriptor[:canonical_name]}"),
456
+ canonical_name: descriptor[:canonical_name],
457
+ kind: descriptor[:kind],
458
+ display_name: descriptor[:canonical_name],
459
+ last_seen_at: Time.now.utc,
460
+ created_at: Time.now.utc,
461
+ updated_at: Time.now.utc
462
+ }, operation: 'official_record_writer.identity_principal')
428
463
  table[id: id]
429
464
  rescue Sequel::UniqueConstraintViolation => e
430
465
  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
466
+ existing = table.where(canonical_name: descriptor[:canonical_name], kind: descriptor[:kind]).first
467
+ return existing if existing
468
+
469
+ raise
432
470
  end
433
471
 
434
- def find_or_create_portable_identity(db, principal, provider, descriptor)
435
- table = db[:portable_identities]
472
+ def find_or_create_identity(db, principal, provider, descriptor)
473
+ table = db[:identities]
436
474
  existing = table.where(
437
475
  principal_id: principal[:id],
438
476
  provider_id: provider[:id],
@@ -440,29 +478,32 @@ module Legion
440
478
  ).first
441
479
  return existing if existing
442
480
 
443
- uuid_key = "portable_identity:#{principal[:id]}:#{provider[:id]}:#{descriptor[:provider_identity_key]}"
444
- id = insert_row(db, :portable_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.portable_identity')
481
+ uuid_key = "identity:#{principal[:id]}:#{provider[:id]}:#{descriptor[:provider_identity_key]}"
482
+ id = insert_with_savepoint(db, :identities, {
483
+ uuid: deterministic_uuid(uuid_key),
484
+ principal_id: principal[:id],
485
+ provider_id: provider[:id],
486
+ provider_identity_key: descriptor[:provider_identity_key],
487
+ last_authenticated_at: Time.now.utc,
488
+ account_type: 'primary',
489
+ is_default: true,
490
+ created_at: Time.now.utc,
491
+ updated_at: Time.now.utc
492
+ }, operation: 'official_record_writer.identity')
455
493
  table[id: id]
456
494
  rescue Sequel::UniqueConstraintViolation => e
457
- handle_exception(e, level: :debug, handled: true, operation: 'official_record_writer.portable_identity_race')
458
- table.where(principal_id: principal[:id], provider_id: provider[:id],
459
- provider_identity_key: descriptor[:provider_identity_key]).first
495
+ handle_exception(e, level: :debug, handled: true, operation: 'official_record_writer.identity_race')
496
+ existing = table.where(principal_id: principal[:id], provider_id: provider[:id],
497
+ provider_identity_key: descriptor[:provider_identity_key]).first
498
+ return existing if existing
499
+
500
+ raise
460
501
  end
461
502
 
462
503
  def identity_tables_available?(db)
463
- db.table_exists?(:portable_identity_providers) &&
464
- db.table_exists?(:portable_identity_principals) &&
465
- db.table_exists?(:portable_identities)
504
+ db.table_exists?(:identity_providers) &&
505
+ db.table_exists?(:identity_principals) &&
506
+ db.table_exists?(:identities)
466
507
  end
467
508
 
468
509
  def normalize_caller_type(value)
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.2.9
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity