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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f3b320363f209f8dfae3ad1a9757bae5e17dd4d7e6327b849b0d8804df22c07
4
- data.tar.gz: ac12848278ac407233eb43b6f5a72dbf503b030a9d404e88578fdb8ebd173cc5
3
+ metadata.gz: bc598e354968c28944d6e897feb5853d57987fed49eaf0cd16f319abcfbd8cf8
4
+ data.tar.gz: ebc17c4d4b8c49585bcdb0a49803c4988e8d55970ddde56b294746d810193f63
5
5
  SHA512:
6
- metadata.gz: 0e0331f06f446400f70581981a8f083e1baac336aee984904f56af60eb9a9d021d56af48c8b845a13beaebd4aca1ba0e7a264af1101f78426d447a4cfd3d86b2
7
- data.tar.gz: 70b599bee80873cd661bdd09aacb406da9d8cdd479d270b6012032945aedb08bb82a3819f00f11f44e45b3470b8b88042491e5e892900564d334d6b4748f945a
6
+ metadata.gz: 7f36631ecd03c493fa2dfd3b347a4f16765b7900545d31306e4b73f0418a455a99c15ab55a8de83ad86c123befede09a222a67a6095f624923028c585ca9c7d0
7
+ data.tar.gz: 9a1783c655df6dce9c23a4a878645cf196e4463d0763d95c380029263a8deeccce7cfce8e66ede4843a04c68e8369232d8d4b785460bd70181d4ad1adcf1c01b
data/.rubocop.yml CHANGED
@@ -30,6 +30,8 @@ Metrics/BlockLength:
30
30
  - 'lib/legion/data/migrations/*'
31
31
  ThreadSafety/ClassInstanceVariable:
32
32
  Enabled: false
33
+ Legion/Llm/TaxonomyEnum:
34
+ Enabled: false
33
35
  ThreadSafety/ClassAndModuleAttributes:
34
36
  Enabled: false
35
37
  Metrics/AbcSize:
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
@@ -97,18 +97,15 @@ module Legion
97
97
  total_rows = 0
98
98
  paths = []
99
99
 
100
- loop do
101
- batch_result = archive_batch(
102
- conn: conn,
103
- table: table,
104
- cutoff: cutoff,
105
- batch_size: batch_size,
106
- batch_n: batches + 1,
107
- now: now,
108
- storage_backend: storage_backend
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: :debug, handled: true, operation: :extract_handler_available, handler: name, gem: gem_name)
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: :debug, handled: true, operation: :embedding_vector, id: self[:id])
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: :debug, handled: true, operation: :parsed_metrics, id: self[:id])
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: :debug, handled: true, operation: :parsed_hosted_worker_ids, id: self[:id])
29
+ handle_exception(e, level: :warn, handled: true, operation: :parsed_hosted_worker_ids, id: self[:id])
30
30
  []
31
31
  end
32
32
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Data
5
- VERSION = '1.10.5'
5
+ VERSION = '1.10.7'
6
6
  end
7
7
  end
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: :debug, handled: true, operation: :connected?)
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.5
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