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 +4 -4
- data/CHANGELOG.md +14 -0
- data/lib/legion/extensions/llm/ledger/actors/metering.rb +2 -0
- data/lib/legion/extensions/llm/ledger/actors/prompts.rb +2 -0
- data/lib/legion/extensions/llm/ledger/actors/tools.rb +2 -0
- data/lib/legion/extensions/llm/ledger/backfill/legacy_llm_records.rb +1 -1
- data/lib/legion/extensions/llm/ledger/helpers/persistence_logging.rb +5 -3
- data/lib/legion/extensions/llm/ledger/version.rb +1 -1
- data/lib/legion/extensions/llm/ledger/writers/official_record_writer.rb +187 -151
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 84be271a1d8b2960d1eeef98a01c950ed2d44cab4e2e6d4c8dd6708980d027d4
|
|
4
|
+
data.tar.gz: 6da1a77c998891e3b2e1a5fae24b1109c51715c762b52aa308ebb73abb886188
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
@@ -184,7 +184,7 @@ module Legion
|
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
def official_metric_uuid(payload)
|
|
187
|
-
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
|
-
|
|
28
|
-
|
|
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,
|
|
@@ -42,8 +42,7 @@ module Legion
|
|
|
42
42
|
|
|
43
43
|
db.transaction do
|
|
44
44
|
conversation = find_or_create_conversation(db, body)
|
|
45
|
-
|
|
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 =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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 =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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 =
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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, :
|
|
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 =
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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 =
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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 =
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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 =
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
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)
|