legion-data 1.10.3 → 1.10.6

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: 2489246ac60704ae24678b24c75ab7d2e8f4153d332f3ad1d61f062a4e5c1c9c
4
- data.tar.gz: 03e7b2217887a809525bdbf10337af382b1059638ce04c65c3c6bca31f517cc2
3
+ metadata.gz: 861fe6533a77e8819c03400430f32ddb420a7cfa29e042b3c86734f6d7fd57bc
4
+ data.tar.gz: 48fe377db280cad64c5e9331926a24319a44c78c96eeb9aa7e2c6aa038a77024
5
5
  SHA512:
6
- metadata.gz: 5bd7a8abc3a69bcf9ad7c7da89a3937057af7e10516b2070ea60eb0d893b9f09c83570b3535af5949ce0bf3138a7b4e6cd5302eac0bd0b09b259da84e432a9dd
7
- data.tar.gz: b934c3058f683f0905a84590f41eb0da21db6ba8c9d221201e5a444c47e486c3cec1dedc14f058ebff7ea8c0637fe67d9bbbae66b108d211529909db3ef4e968
6
+ metadata.gz: 3850a5774dbc1de772fd8a2b4762dddc9da3164e9cff32b5f02b946f7608dba551394bcb3b2aa90590f038199f33da655957c74896ff74419e840a4769b7008e
7
+ data.tar.gz: 006b1ed6fd955a876dabfa6875fa075eb8abfec1aa59467b3411e36e3ce32752735277b4013ba908db280cb98a4c152bb91e14563be55c41d6ff53778c3f944a
data/.rubocop.yml CHANGED
@@ -23,6 +23,13 @@ Naming/VariableNumber:
23
23
  - lib/legion/data/connection.rb
24
24
  Legion/Framework/EagerSequelModel:
25
25
  Enabled: false
26
+ # TaxonomyEnum validates LLM lane taxonomy (:tier/:type/:circuit_state). In
27
+ # these paths `type:` is a Sequel column type (e.g. foreign_key type: :uuid) or
28
+ # the extract API (type: :auto/:text) — not LLM taxonomy — so the cop misfires.
29
+ Legion/Llm/TaxonomyEnum:
30
+ Exclude:
31
+ - 'lib/legion/data/migrations/**/*'
32
+ - 'spec/**/*'
26
33
  Metrics/BlockLength:
27
34
  Max: 100
28
35
  Exclude:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Legion::Data Changelog
2
2
 
3
+ ## [1.10.6] - 2026-07-03
4
+
5
+ ### Changed
6
+ - 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.
7
+
8
+ ## [1.10.5] - 2026-06-16
9
+
10
+ ### Added
11
+ - Migration 135: adds context token accounting columns to `llm_message_inference_metrics` — `llm_message_inference_metrics` is now the canonical source of truth for all pipeline context token metrics (request messages, loaded/curated/archived history, thinking strip savings, context-window enforcement savings, RAG injection, system/baseline prompt, tool definitions, final context estimate). Includes `context_accounting_status` and `context_accounting_json` for provenance.
12
+ - Migration 135: creates `llm_context_accounting_events` table for drill-down evidence rows (not a second source of token truth — totals reconcile to the canonical metrics row).
13
+ - Model: `Legion::Data::Models::LLM::ContextAccountingEvent` with foreign key associations to request, response, and metric.
14
+ - Association: `MessageInferenceMetric#context_accounting_events`.
15
+
16
+ ## [1.10.4] - 2026-06-12
17
+
18
+ ### Added
19
+ - Migration 134: adds `operation` (String 64), `dispatch_path` (String 32), and `idempotency_key` (String 128) columns to `llm_route_attempts` with indexes on `operation` and `idempotency_key`. Enables per-attempt tracking of the LLM operation type, routing path, and deduplication key.
20
+
3
21
  ## [1.10.3] - 2026-06-10
4
22
 
5
23
  ### Changed
@@ -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]
@@ -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,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ alter_table(:llm_message_inference_metrics) do
6
+ add_column :request_message_estimated_tokens, Integer, null: false, default: 0
7
+ add_column :loaded_history_estimated_tokens, Integer, null: false, default: 0
8
+ add_column :curated_history_estimated_tokens, Integer, null: false, default: 0
9
+ add_column :curation_saved_estimated_tokens, Integer, null: false, default: 0
10
+ add_column :stripped_thinking_estimated_tokens, Integer, null: false, default: 0
11
+ add_column :archived_history_estimated_tokens, Integer, null: false, default: 0
12
+ add_column :archive_saved_estimated_tokens, Integer, null: false, default: 0
13
+ add_column :context_window_saved_estimated_tokens, Integer, null: false, default: 0
14
+ add_column :rag_injected_estimated_tokens, Integer, null: false, default: 0
15
+ add_column :system_prompt_estimated_tokens, Integer, null: false, default: 0
16
+ add_column :baseline_system_estimated_tokens, Integer, null: false, default: 0
17
+ add_column :tool_definition_estimated_tokens, Integer, null: false, default: 0
18
+ add_column :final_context_estimated_tokens, Integer, null: false, default: 0
19
+ add_column :loaded_history_message_count, Integer, null: false, default: 0
20
+ add_column :curated_history_message_count, Integer, null: false, default: 0
21
+ add_column :archived_history_message_count, Integer, null: false, default: 0
22
+ add_column :stripped_thinking_message_count, Integer, null: false, default: 0
23
+ add_column :context_window_message_count_before, Integer, null: false, default: 0
24
+ add_column :context_window_message_count_after, Integer, null: false, default: 0
25
+ add_column :rag_entry_count, Integer, null: false, default: 0
26
+ add_column :tool_definition_count, Integer, null: false, default: 0
27
+ add_column :context_accounting_status, String, size: 64, null: false, default: 'missing'
28
+ add_column :context_accounting_json, String, text: true
29
+ end
30
+
31
+ create_table(:llm_context_accounting_events) do
32
+ primary_key :id
33
+ String :uuid, size: 36, null: false, unique: true
34
+ foreign_key :message_inference_request_id, :llm_message_inference_requests, null: false, on_delete: :cascade
35
+ foreign_key :message_inference_response_id, :llm_message_inference_responses, null: true, on_delete: :set_null
36
+ foreign_key :message_inference_metric_id, :llm_message_inference_metrics, null: true, on_delete: :set_null
37
+ String :conversation_ref, size: 128
38
+ String :request_ref, size: 128, null: false
39
+ String :event_type, size: 64, null: false
40
+ String :component, size: 64, null: false
41
+ Integer :estimated_tokens_before, null: false, default: 0
42
+ Integer :estimated_tokens_after, null: false, default: 0
43
+ Integer :estimated_tokens_delta, null: false, default: 0
44
+ Integer :message_count_before, null: false, default: 0
45
+ Integer :message_count_after, null: false, default: 0
46
+ String :metadata_json, text: true
47
+ DateTime :recorded_at
48
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
49
+
50
+ index :message_inference_request_id
51
+ index :message_inference_response_id
52
+ index :message_inference_metric_id
53
+ index :request_ref
54
+ index :conversation_ref
55
+ index %i[event_type component]
56
+ index :recorded_at
57
+ end
58
+ end
59
+
60
+ down do
61
+ drop_table(:llm_context_accounting_events)
62
+
63
+ alter_table(:llm_message_inference_metrics) do
64
+ drop_column :context_accounting_json
65
+ drop_column :context_accounting_status
66
+ drop_column :tool_definition_count
67
+ drop_column :rag_entry_count
68
+ drop_column :context_window_message_count_after
69
+ drop_column :context_window_message_count_before
70
+ drop_column :stripped_thinking_message_count
71
+ drop_column :archived_history_message_count
72
+ drop_column :curated_history_message_count
73
+ drop_column :loaded_history_message_count
74
+ drop_column :final_context_estimated_tokens
75
+ drop_column :tool_definition_estimated_tokens
76
+ drop_column :baseline_system_estimated_tokens
77
+ drop_column :system_prompt_estimated_tokens
78
+ drop_column :rag_injected_estimated_tokens
79
+ drop_column :context_window_saved_estimated_tokens
80
+ drop_column :archive_saved_estimated_tokens
81
+ drop_column :archived_history_estimated_tokens
82
+ drop_column :stripped_thinking_estimated_tokens
83
+ drop_column :curation_saved_estimated_tokens
84
+ drop_column :curated_history_estimated_tokens
85
+ drop_column :loaded_history_estimated_tokens
86
+ drop_column :request_message_estimated_tokens
87
+ end
88
+ end
89
+ 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
@@ -21,7 +21,8 @@ module Legion
21
21
  rbac/role_assignments rbac/runner_grants rbac/cross_team_grants
22
22
  llm/conversation llm/message llm/message_inference_request
23
23
  llm/message_inference_response llm/route_attempt
24
- llm/message_inference_metric llm/tool_call llm/tool_call_attempt
24
+ llm/message_inference_metric llm/context_accounting_event
25
+ llm/tool_call llm/tool_call_attempt
25
26
  llm/conversation_compaction llm/policy_evaluation
26
27
  llm/security_event llm/registry_event]
27
28
  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
 
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'model_helpers'
4
+
5
+ module Legion
6
+ module Data
7
+ module Models
8
+ module LLM
9
+ class ContextAccountingEvent < Sequel::Model(:llm_context_accounting_events)
10
+ include ModelHelpers
11
+
12
+ many_to_one :message_inference_request
13
+ many_to_one :message_inference_response
14
+ many_to_one :message_inference_metric
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -11,6 +11,7 @@ module Legion
11
11
 
12
12
  many_to_one :message_inference_request
13
13
  many_to_one :message_inference_response
14
+ one_to_many :context_accounting_events
14
15
 
15
16
  class << self
16
17
  def finance_usage_by_cost_center_model_day(cost_center: nil, model_key: nil, from: nil, to: nil)
@@ -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.3'
5
+ VERSION = '1.10.6'
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.3
4
+ version: 1.10.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -281,6 +281,8 @@ files:
281
281
  - lib/legion/data/migrations/132_drop_schema_version_from_llm_tool_calls.rb
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
+ - lib/legion/data/migrations/135_add_llm_context_token_accounting.rb
285
+ - lib/legion/data/migrations/136_drop_llm_messages_seq_unique.rb
284
286
  - lib/legion/data/model.rb
285
287
  - lib/legion/data/models/apollo/access_log.rb
286
288
  - lib/legion/data/models/apollo/entries.rb
@@ -306,6 +308,7 @@ files:
306
308
  - lib/legion/data/models/identity/model_helpers.rb
307
309
  - lib/legion/data/models/identity/principal.rb
308
310
  - lib/legion/data/models/identity/providers.rb
311
+ - lib/legion/data/models/llm/context_accounting_event.rb
309
312
  - lib/legion/data/models/llm/conversation.rb
310
313
  - lib/legion/data/models/llm/conversation_compaction.rb
311
314
  - lib/legion/data/models/llm/message.rb