legion-data 1.10.5 → 1.10.7
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/.rubocop.yml +2 -0
- data/CHANGELOG.md +9 -0
- data/lib/legion/data/archiver.rb +9 -12
- data/lib/legion/data/connection.rb +20 -0
- data/lib/legion/data/extract/handlers/base.rb +1 -1
- data/lib/legion/data/migrations/136_drop_llm_messages_seq_unique.rb +36 -0
- data/lib/legion/data/models/function.rb +1 -1
- data/lib/legion/data/models/node.rb +2 -2
- data/lib/legion/data/version.rb +1 -1
- data/lib/legion/data.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc598e354968c28944d6e897feb5853d57987fed49eaf0cd16f319abcfbd8cf8
|
|
4
|
+
data.tar.gz: ebc17c4d4b8c49585bcdb0a49803c4988e8d55970ddde56b294746d810193f63
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7f36631ecd03c493fa2dfd3b347a4f16765b7900545d31306e4b73f0418a455a99c15ab55a8de83ad86c123befede09a222a67a6095f624923028c585ca9c7d0
|
|
7
|
+
data.tar.gz: 9a1783c655df6dce9c23a4a878645cf196e4463d0763d95c380029263a8deeccce7cfce8e66ede4843a04c68e8369232d8d4b785460bd70181d4ad1adcf1c01b
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Legion::Data Changelog
|
|
2
2
|
|
|
3
|
+
## [1.10.7] - 2026-07-15
|
|
4
|
+
### Fixed
|
|
5
|
+
- Fail loud with actionable error when unresolved lease:// credentials reach connection
|
|
6
|
+
|
|
7
|
+
## [1.10.6] - 2026-07-03
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Migration 136: drops the `(conversation_id, seq)` UNIQUE constraint on `llm_messages`, replacing it with a NON-unique composite index. A conversation is a tree, not a line — a single parent can legitimately have two children at the same depth (e.g. a failed branch and the branch that continued), so two distinct messages can share `(conversation_id, seq)` and that is truth, not a duplicate. The unique constraint rejected legitimate siblings and forced the centralized ledger into a racy `MAX(seq)+1` generation that collided under concurrent writers (`PG::UniqueViolation` on `llm_messages_conversation_id_seq_key`). Message identity remains guaranteed by the unique `uuid`; `seq` becomes descriptive tree-depth. On SQLite the constraint drop rebuilds the table (dropping the inline `uuid` UNIQUE), so the migration re-asserts the uuid unique index — a no-op on PostgreSQL where `DROP CONSTRAINT` is surgical.
|
|
11
|
+
|
|
3
12
|
## [1.10.5] - 2026-06-16
|
|
4
13
|
|
|
5
14
|
### Added
|
data/lib/legion/data/archiver.rb
CHANGED
|
@@ -97,18 +97,15 @@ module Legion
|
|
|
97
97
|
total_rows = 0
|
|
98
98
|
paths = []
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
)
|
|
110
|
-
break unless batch_result
|
|
111
|
-
|
|
100
|
+
while (batch_result = archive_batch(
|
|
101
|
+
conn: conn,
|
|
102
|
+
table: table,
|
|
103
|
+
cutoff: cutoff,
|
|
104
|
+
batch_size: batch_size,
|
|
105
|
+
batch_n: batches + 1,
|
|
106
|
+
now: now,
|
|
107
|
+
storage_backend: storage_backend
|
|
108
|
+
))
|
|
112
109
|
batches += 1
|
|
113
110
|
total_rows += batch_result[:row_count]
|
|
114
111
|
paths << batch_result[:path]
|
|
@@ -10,6 +10,10 @@ module Legion
|
|
|
10
10
|
module Connection
|
|
11
11
|
ADAPTERS = %i[sqlite mysql2 postgres].freeze
|
|
12
12
|
|
|
13
|
+
UNRESOLVED_URI_PATTERN = %r{\A(?:vault|env|lease)://}
|
|
14
|
+
|
|
15
|
+
class UnresolvedCredentialError < StandardError; end
|
|
16
|
+
|
|
13
17
|
GENERIC_KEYS = %i[max_connections pool_timeout preconnect single_threaded test name].freeze
|
|
14
18
|
|
|
15
19
|
ADAPTER_KEYS = {
|
|
@@ -171,6 +175,9 @@ module Legion
|
|
|
171
175
|
attempted_adapter = adapter
|
|
172
176
|
begin
|
|
173
177
|
::Sequel.connect(connection_opts_for(adapter: attempted_adapter, opts: opts))
|
|
178
|
+
rescue UnresolvedCredentialError => e
|
|
179
|
+
handle_exception(e, level: :fatal, handled: false, operation: :data_connect)
|
|
180
|
+
raise
|
|
174
181
|
rescue StandardError => e
|
|
175
182
|
raise unless dev_fallback?
|
|
176
183
|
|
|
@@ -410,10 +417,23 @@ module Legion
|
|
|
410
417
|
|
|
411
418
|
def connection_opts_for(adapter:, opts:)
|
|
412
419
|
connection_opts = opts.merge(adapter: adapter, **creds_builder)
|
|
420
|
+
validate_no_unresolved_uris!(connection_opts)
|
|
413
421
|
connection_opts[:preconnect] = false if adapter != :sqlite && dev_fallback?
|
|
414
422
|
connection_opts
|
|
415
423
|
end
|
|
416
424
|
|
|
425
|
+
def validate_no_unresolved_uris!(connection_opts)
|
|
426
|
+
%i[user username password].each do |key|
|
|
427
|
+
value = connection_opts[key]
|
|
428
|
+
next unless value.is_a?(String) && value.match?(UNRESOLVED_URI_PATTERN)
|
|
429
|
+
|
|
430
|
+
raise UnresolvedCredentialError,
|
|
431
|
+
"Legion::Data cannot connect: settings[:data][:creds][:#{key}] is still " \
|
|
432
|
+
"an unresolved URI placeholder (#{value}). The settings resolver failed to " \
|
|
433
|
+
'resolve this lease — check that Legion::Crypt and Vault are available at boot.'
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
417
437
|
def sequel_opts
|
|
418
438
|
data = Legion::Settings[:data]
|
|
419
439
|
opts = {}
|
|
@@ -53,7 +53,7 @@ module Legion
|
|
|
53
53
|
require gem_name
|
|
54
54
|
true
|
|
55
55
|
rescue LoadError => e
|
|
56
|
-
handle_exception(e, level: :
|
|
56
|
+
handle_exception(e, level: :warn, handled: true, operation: :extract_handler_available, handler: name, gem: gem_name)
|
|
57
57
|
false
|
|
58
58
|
end
|
|
59
59
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The (conversation_id, seq) UNIQUE constraint modeled a conversation as a flat
|
|
4
|
+
# line, but a conversation is a tree: a single parent can legitimately have two
|
|
5
|
+
# children at the same depth (e.g. a failed branch and the branch that
|
|
6
|
+
# succeeded), so two distinct messages can share the same (conversation_id, seq)
|
|
7
|
+
# and that is TRUTH, not a duplicate. The unique constraint rejected those
|
|
8
|
+
# legitimate siblings and forced the ledger into a racy MAX(seq)+1 generation
|
|
9
|
+
# that collided under concurrent writers.
|
|
10
|
+
#
|
|
11
|
+
# Message identity is guaranteed by the unique `uuid` (producer-derived, stable
|
|
12
|
+
# across redelivery). seq/parent become descriptive ordering data, so we keep a
|
|
13
|
+
# NON-unique composite index for conversation-ordered reads.
|
|
14
|
+
Sequel.migration do
|
|
15
|
+
up do
|
|
16
|
+
alter_table(:llm_messages) do
|
|
17
|
+
drop_constraint(:llm_messages_conversation_id_seq_key, type: :unique)
|
|
18
|
+
add_index %i[conversation_id seq], name: :idx_llm_messages_conversation_seq
|
|
19
|
+
end
|
|
20
|
+
# SQLite emulates the constraint drop by rebuilding the table, which drops
|
|
21
|
+
# the inline `uuid` UNIQUE. Re-assert it so message identity/dedup is never
|
|
22
|
+
# lost. On PostgreSQL DROP CONSTRAINT is surgical and the original uuid index
|
|
23
|
+
# already exists, so this is a no-op there.
|
|
24
|
+
alter_table(:llm_messages) do
|
|
25
|
+
add_index :uuid, unique: true, name: :idx_llm_messages_uuid_unique, if_not_exists: true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
down do
|
|
30
|
+
alter_table(:llm_messages) do
|
|
31
|
+
drop_index :uuid, name: :idx_llm_messages_uuid_unique, if_exists: true
|
|
32
|
+
drop_index %i[conversation_id seq], name: :idx_llm_messages_conversation_seq
|
|
33
|
+
add_unique_constraint %i[conversation_id seq], name: :llm_messages_conversation_id_seq_key
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -18,7 +18,7 @@ module Legion
|
|
|
18
18
|
|
|
19
19
|
::JSON.parse(embedding)
|
|
20
20
|
rescue ::JSON::ParserError => e
|
|
21
|
-
handle_exception(e, level: :
|
|
21
|
+
handle_exception(e, level: :warn, handled: true, operation: :embedding_vector, id: self[:id])
|
|
22
22
|
nil
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -17,7 +17,7 @@ module Legion
|
|
|
17
17
|
|
|
18
18
|
Legion::JSON.load(metrics)
|
|
19
19
|
rescue StandardError => e
|
|
20
|
-
handle_exception(e, level: :
|
|
20
|
+
handle_exception(e, level: :warn, handled: true, operation: :parsed_metrics, id: self[:id])
|
|
21
21
|
nil
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -26,7 +26,7 @@ module Legion
|
|
|
26
26
|
|
|
27
27
|
Legion::JSON.load(hosted_worker_ids)
|
|
28
28
|
rescue StandardError => e
|
|
29
|
-
handle_exception(e, level: :
|
|
29
|
+
handle_exception(e, level: :warn, handled: true, operation: :parsed_hosted_worker_ids, id: self[:id])
|
|
30
30
|
[]
|
|
31
31
|
end
|
|
32
32
|
end
|
data/lib/legion/data/version.rb
CHANGED
data/lib/legion/data.rb
CHANGED
|
@@ -112,7 +112,7 @@ module Legion
|
|
|
112
112
|
def connected?
|
|
113
113
|
Legion::Settings[:data][:connected] == true
|
|
114
114
|
rescue StandardError => e
|
|
115
|
-
handle_exception(e, level: :
|
|
115
|
+
handle_exception(e, level: :warn, handled: true, operation: :connected?)
|
|
116
116
|
false
|
|
117
117
|
end
|
|
118
118
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legion-data
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.10.
|
|
4
|
+
version: 1.10.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -282,6 +282,7 @@ files:
|
|
|
282
282
|
- lib/legion/data/migrations/133_allow_null_context_tokens.rb
|
|
283
283
|
- lib/legion/data/migrations/134_add_route_attempt_columns.rb
|
|
284
284
|
- lib/legion/data/migrations/135_add_llm_context_token_accounting.rb
|
|
285
|
+
- lib/legion/data/migrations/136_drop_llm_messages_seq_unique.rb
|
|
285
286
|
- lib/legion/data/model.rb
|
|
286
287
|
- lib/legion/data/models/apollo/access_log.rb
|
|
287
288
|
- lib/legion/data/models/apollo/entries.rb
|