legion-data 1.8.8 → 1.9.0
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 +43 -18
- data/CHANGELOG.md +17 -0
- data/CLAUDE.md +128 -15
- data/lib/legion/data/connection.rb +6 -1
- data/lib/legion/data/local.rb +4 -2
- data/lib/legion/data/migrations/116_make_tool_calls_response_id_nullable.rb +21 -0
- data/lib/legion/data/migrations/117_add_conversation_id_to_llm_tool_calls.rb +22 -0
- data/lib/legion/data/migrations/118_add_entity_type_to_audit_records.rb +29 -0
- data/lib/legion/data/migrations/119_create_missing_apollo_tables.rb +85 -0
- data/lib/legion/data/migrations/120_add_missing_apollo_indexes.rb +73 -0
- data/lib/legion/data/migrations/121_add_cache_token_metrics.rb +23 -0
- data/lib/legion/data/migrations/122_ensure_runtime_caller_columns.rb +36 -0
- data/lib/legion/data/migrations/123_add_llm_tool_calls_audit_columns.rb +17 -0
- data/lib/legion/data/migrations/124_add_llm_tool_call_attempts_audit_columns.rb +11 -0
- data/lib/legion/data/migrations/125_add_llm_escalation_events_audit_columns.rb +25 -0
- data/lib/legion/data/migrations/126_add_llm_message_inference_responses_audit_columns.rb +13 -0
- data/lib/legion/data/migrations/127_add_llm_message_inference_requests_audit_columns.rb +12 -0
- data/lib/legion/data/migrations/128_add_identity_columns_to_shared_tables.rb +29 -0
- data/lib/legion/data/models/apollo/model_helpers.rb +2 -1
- data/lib/legion/data/models/llm/tool_call.rb +1 -0
- data/lib/legion/data/spool.rb +2 -1
- data/lib/legion/data/version.rb +1 -1
- metadata +14 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 92fba520187d3940bae5ac79e43f2155b16a0c7e3949e664233dd012871d56e7
|
|
4
|
+
data.tar.gz: da5ff3d58c176ccb65fc03bdc6855318acba28b4974cf76e8b986eec2b70d5c3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 748443ee5df5494f60e080a6ad6405df97ed4166ed71e58fc58159b540ab2c9748195447c2a05e697a5ecc00889b17d7ff84337d0b718644c27c1312a6c47ab5
|
|
7
|
+
data.tar.gz: 9417467c46a3a4b1651c68760ff682b07648cc021d51601bc844fb1afdccf68ae0a807f897823a818450658e8503feea7e409ecaacd2b80c757dd0847a037b22
|
data/.rubocop.yml
CHANGED
|
@@ -1,31 +1,56 @@
|
|
|
1
1
|
inherit_gem:
|
|
2
2
|
rubocop-legion: config/core.yml
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Max:
|
|
3
|
+
AllCops:
|
|
4
|
+
TargetRubyVersion: 3.4
|
|
5
|
+
NewCops: enable
|
|
6
|
+
SuggestExtensions: false
|
|
7
|
+
Layout/LineLength:
|
|
8
|
+
Max: 160
|
|
9
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
|
10
|
+
EnforcedStyle: space
|
|
11
|
+
Layout/HashAlignment:
|
|
12
|
+
EnforcedHashRocketStyle: table
|
|
13
|
+
EnforcedColonStyle: table
|
|
14
|
+
Metrics/MethodLength:
|
|
15
|
+
Max: 50
|
|
16
|
+
Metrics/ClassLength:
|
|
17
|
+
Max: 1500
|
|
18
|
+
Metrics/ModuleLength:
|
|
19
|
+
Max: 1500
|
|
20
|
+
Naming/VariableNumber:
|
|
9
21
|
Exclude:
|
|
10
22
|
- 'spec/**/*'
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
Naming/VariableNumber:
|
|
23
|
+
- lib/legion/data/connection.rb
|
|
24
|
+
Legion/Framework/EagerSequelModel:
|
|
14
25
|
Enabled: false
|
|
15
|
-
|
|
16
|
-
|
|
26
|
+
Metrics/BlockLength:
|
|
27
|
+
Max: 100
|
|
17
28
|
Exclude:
|
|
18
|
-
- '
|
|
19
|
-
|
|
20
|
-
# Pre-existing patterns — suppress until addressed in a dedicated cleanup PR
|
|
29
|
+
- 'spec/**/*'
|
|
30
|
+
- 'lib/legion/data/migrations/*'
|
|
21
31
|
ThreadSafety/ClassInstanceVariable:
|
|
22
32
|
Enabled: false
|
|
23
|
-
|
|
24
33
|
ThreadSafety/ClassAndModuleAttributes:
|
|
25
34
|
Enabled: false
|
|
35
|
+
Metrics/AbcSize:
|
|
36
|
+
Max: 60
|
|
37
|
+
Exclude:
|
|
38
|
+
- 'spec/**/*'
|
|
39
|
+
Metrics/CyclomaticComplexity:
|
|
40
|
+
Max: 15
|
|
41
|
+
Exclude:
|
|
42
|
+
- 'spec/**/*'
|
|
43
|
+
Metrics/PerceivedComplexity:
|
|
44
|
+
Max: 17
|
|
45
|
+
Exclude:
|
|
46
|
+
- 'spec/**/*'
|
|
26
47
|
|
|
27
|
-
|
|
48
|
+
Style/Documentation:
|
|
28
49
|
Enabled: false
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
Style/SymbolArray:
|
|
51
|
+
Enabled: true
|
|
52
|
+
Style/FrozenStringLiteralComment:
|
|
53
|
+
Enabled: true
|
|
54
|
+
EnforcedStyle: always
|
|
55
|
+
Naming/PredicateMethod:
|
|
31
56
|
Enabled: false
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Legion::Data Changelog
|
|
2
2
|
|
|
3
|
+
## [1.9.0] - 2026-06-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Migration 123: audit columns on `llm_tool_calls` — `tool_arguments_json` (TEXT), `tool_result_json` (TEXT), `tool_category` (String(64)), `data_handling_classification` (String(32)), `policy_decision` (String(32)), `requires_human_approval` (Boolean) plus indexes on `tool_category`, `data_handling_classification`, `policy_decision`.
|
|
8
|
+
- Migration 124: audit columns on `llm_tool_call_attempts` — `attempt_input_json` (TEXT), `attempt_output_json` (TEXT), `error_details_json` (TEXT).
|
|
9
|
+
- Migration 125: audit columns on `llm_escalation_events` — `history_json` (TEXT), `outcome` (String(32)), `total_attempts` (Integer) plus index on `outcome`.
|
|
10
|
+
- Migration 126: audit columns on `llm_message_inference_responses` — `route_attempts` (Integer, default 0), `escalation_chain_ref` (String(128)) plus index on `escalation_chain_ref`. Skips `response_content_hash` (already exists since migration 080).
|
|
11
|
+
- Migration 127: audit columns on `llm_message_inference_requests` — `parent_request_id` (Integer, self-referencing FK on_delete: :set_null). Skips `request_content_hash`, `curation_strategy`, `tool_policy` (all already exist since migration 079).
|
|
12
|
+
|
|
13
|
+
## [1.8.9] - 2026-05-26
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Migration 116: make `llm_tool_calls.message_inference_response_id` nullable and drop composite unique index on `[message_inference_response_id, tool_call_index]`. Eliminates 30-40% dead-letter rate on tool audit messages caused by AMQP race between response and tool call writers.
|
|
17
|
+
- Migration 117: add nullable `conversation_id` FK to `llm_tool_calls` referencing `llm_conversations`, so tool call rows can track their conversation even when the response row hasn't been written yet.
|
|
18
|
+
- Add `many_to_one :conversation` association to `LLM::ToolCall` model.
|
|
19
|
+
|
|
3
20
|
## [1.8.8] - 2026-05-20
|
|
4
21
|
|
|
5
22
|
### Added
|
data/CLAUDE.md
CHANGED
|
@@ -32,11 +32,116 @@ jq '[.examples[] | select(.status != "passed") | {file_path, line_number, full_d
|
|
|
32
32
|
|
|
33
33
|
- Never edit published migrations. Add a new migration.
|
|
34
34
|
- Do not guard migrations with `create_table?`, `drop_table?`, `table_exists?`, `if_exists`, `if_not_exists`, `next if`, or `next unless`.
|
|
35
|
-
-
|
|
35
|
+
- **One change per migration file.** Each migration modifies exactly ONE table. Never loop over tables. If a migration fails, you must be able to identify exactly what broke and roll back cleanly.
|
|
36
|
+
- Never use `.each`, `.map`, or any iterator in a migration. If 12 tables need the same column, that's 12 migration files.
|
|
37
|
+
- Never use raw SQL (`run '...'`) when Sequel DSL supports the operation. Use `add_index`, `drop_index`, `add_column`, `drop_column`, etc.
|
|
36
38
|
- Use portable Sequel DSL unless the feature truly requires adapter-specific behavior.
|
|
37
39
|
- Use integer `id` primary keys for joins and public `uuid` columns for APIs/logs/external references.
|
|
38
40
|
- Normalize stable fields. Use JSON only for genuinely dynamic provider payloads or evidence.
|
|
39
41
|
|
|
42
|
+
### Sequel Migration DSL Reference
|
|
43
|
+
|
|
44
|
+
**Create table**: https://sequel.jeremyevans.net/rdoc/classes/Sequel/Database.html#method-i-create_table
|
|
45
|
+
**Column options**: https://sequel.jeremyevans.net/rdoc/classes/Sequel/Schema/CreateTableGenerator.html#method-i-column
|
|
46
|
+
|
|
47
|
+
### Create Table Pattern
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
# frozen_string_literal: true
|
|
51
|
+
|
|
52
|
+
Sequel.migration do
|
|
53
|
+
change do
|
|
54
|
+
create_table(:example_records) do
|
|
55
|
+
primary_key :id
|
|
56
|
+
String :uuid, size: 36, null: false, unique: true
|
|
57
|
+
|
|
58
|
+
# Identity columns (required on every table)
|
|
59
|
+
String :access_scope, size: 20, null: false, default: 'global', index: true
|
|
60
|
+
foreign_key :identity_principal_id, :identity_principals, null: true, on_delete: :set_null, on_update: :cascade
|
|
61
|
+
foreign_key :identity_id, :identities, null: true, on_delete: :set_null, on_update: :cascade
|
|
62
|
+
String :identity_canonical_name, size: 255, null: true, index: true
|
|
63
|
+
|
|
64
|
+
# Domain columns here...
|
|
65
|
+
|
|
66
|
+
# Timestamps (required on every table)
|
|
67
|
+
DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP # reflects when the event happened (request/AMQP timestamp)
|
|
68
|
+
DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP # when the row was physically written to the database
|
|
69
|
+
DateTime :updated_at, null: true # set on row update; NULL means never updated
|
|
70
|
+
|
|
71
|
+
index :identity_principal_id
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Alter Table Pattern (adding a column)
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
# frozen_string_literal: true
|
|
81
|
+
|
|
82
|
+
Sequel.migration do
|
|
83
|
+
up do
|
|
84
|
+
alter_table(:target_table) do
|
|
85
|
+
add_column :new_column, String, size: 128, null: true, index: true
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
down do
|
|
90
|
+
alter_table(:target_table) do
|
|
91
|
+
drop_index :new_column
|
|
92
|
+
drop_column :new_column
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Column Option Reference
|
|
99
|
+
|
|
100
|
+
| Option | Purpose |
|
|
101
|
+
|--------|---------|
|
|
102
|
+
| `:null` | `false` = NOT NULL, `true` = nullable |
|
|
103
|
+
| `:default` | Default value (use `Sequel::CURRENT_TIMESTAMP` for timestamps) |
|
|
104
|
+
| `:index` | `true` creates an index on this column; pass a Hash for index options |
|
|
105
|
+
| `:unique` | `true` adds a UNIQUE constraint |
|
|
106
|
+
| `:on_delete` | FK behavior: `:cascade`, `:set_null`, `:restrict`, `:no_action` |
|
|
107
|
+
| `:on_update` | FK behavior: `:cascade`, `:set_null`, `:restrict`, `:no_action` |
|
|
108
|
+
| `:key` | For FKs — the referenced column (unnecessary if referencing primary key) |
|
|
109
|
+
| `:size` | Column width for String/Decimal |
|
|
110
|
+
| `:text` | `true` for TEXT columns (unlimited length) |
|
|
111
|
+
|
|
112
|
+
### Foreign Key Conventions
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
# FK to identity tables — always cascade updates, set null on delete
|
|
116
|
+
foreign_key :identity_principal_id, :identity_principals, null: true, on_delete: :set_null, on_update: :cascade
|
|
117
|
+
foreign_key :identity_id, :identities, null: true, on_delete: :set_null, on_update: :cascade
|
|
118
|
+
|
|
119
|
+
# FK to domain tables — cascade delete (child dies with parent)
|
|
120
|
+
foreign_key :conversation_id, :llm_conversations, null: false, on_delete: :cascade
|
|
121
|
+
|
|
122
|
+
# FK to optional parent — set null on delete (orphan is ok)
|
|
123
|
+
foreign_key :parent_message_id, :llm_messages, null: true, on_delete: :set_null
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Timestamp Semantics
|
|
127
|
+
|
|
128
|
+
| Column | Meaning | Default | Nullable |
|
|
129
|
+
|--------|---------|---------|----------|
|
|
130
|
+
| `created_at` | When the event/action occurred in the real world (e.g. AMQP message timestamp, API request time) | `CURRENT_TIMESTAMP` | NOT NULL |
|
|
131
|
+
| `inserted_at` | When the row was physically written to this database — always DB clock time | `CURRENT_TIMESTAMP` | NOT NULL |
|
|
132
|
+
| `updated_at` | Last time the row was modified after initial insert. NULL means never updated. | none | NULL |
|
|
133
|
+
|
|
134
|
+
`created_at` vs `inserted_at`: a message published at 14:00:00 that gets consumed and written at 14:00:03 has `created_at = 14:00:00` and `inserted_at = 14:00:03`. For synchronous writes they will be the same.
|
|
135
|
+
|
|
136
|
+
### Index Conventions
|
|
137
|
+
|
|
138
|
+
- `access_scope` — always indexed (high cardinality filter for multi-tenant queries)
|
|
139
|
+
- `identity_canonical_name` — always indexed (user-facing search/filter)
|
|
140
|
+
- `identity_principal_id` — always indexed (join path to identity tables)
|
|
141
|
+
- `uuid` — always unique index (external reference lookups)
|
|
142
|
+
- Timestamp columns used in WHERE clauses — indexed
|
|
143
|
+
- Composite indexes for common query patterns: `index [:provider, :model_key]`
|
|
144
|
+
|
|
40
145
|
## Sequel ORM Rules
|
|
41
146
|
|
|
42
147
|
Use Sequel associations as the object graph. References:
|
|
@@ -55,26 +160,30 @@ When Sequel cannot infer names, set `:class`, `:key`, `:primary_key`, `:join_tab
|
|
|
55
160
|
|
|
56
161
|
All new tables in legion-data should follow this column convention. Required fields must be present on every table. Optional fields are added when the domain warrants them.
|
|
57
162
|
|
|
58
|
-
### Required
|
|
163
|
+
### Required (every table, in this order)
|
|
59
164
|
|
|
60
|
-
| Column |
|
|
61
|
-
|
|
62
|
-
| `id` | `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
165
|
+
| Column | Sequel DSL | Purpose |
|
|
166
|
+
|--------|-----------|---------|
|
|
167
|
+
| `id` | `primary_key :id` | Auto-increment integer PK — internal join key, never exposed externally |
|
|
168
|
+
| `uuid` | `String :uuid, size: 36, null: false, unique: true` | External reference — used in APIs, logs, AMQP correlation |
|
|
169
|
+
| `access_scope` | `String :access_scope, size: 20, null: false, default: 'global', index: true` | Multi-tenant scoping (global, personal, team, org) |
|
|
170
|
+
| `identity_principal_id` | `foreign_key :identity_principal_id, :identity_principals, null: true, on_delete: :set_null, on_update: :cascade` | FK to the principal who caused this row |
|
|
171
|
+
| `identity_id` | `foreign_key :identity_id, :identities, null: true, on_delete: :set_null, on_update: :cascade` | FK to the specific provider-bound identity credential |
|
|
172
|
+
| `identity_canonical_name` | `String :identity_canonical_name, size: 255, null: true, index: true` | Point-in-time snapshot of the identity's canonical name. NOT a FK. May become stale if principal is renamed — use FK join for authoritative lookups. |
|
|
173
|
+
| `created_at` | `DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP` | When the event/action occurred (AMQP timestamp, request time) |
|
|
174
|
+
| `inserted_at` | `DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP` | When the row was physically written to the database |
|
|
175
|
+
| `updated_at` | `DateTime :updated_at, null: true` | Set on row update; NULL means never updated after insert |
|
|
68
176
|
|
|
69
177
|
### Optional (add when applicable)
|
|
70
178
|
|
|
71
179
|
| Column | Type | Purpose |
|
|
72
180
|
|--------|------|---------|
|
|
73
|
-
| `expires_at` | `
|
|
74
|
-
| `content_type` | `
|
|
75
|
-
| `conversation_id` | `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
181
|
+
| `expires_at` | `DateTime, null: true` | TTL / archival eligibility |
|
|
182
|
+
| `content_type` | `String, size: 64` | Classifier for the row's payload kind |
|
|
183
|
+
| `conversation_id` | `foreign_key ..., :llm_conversations, on_delete: :cascade` | Links to the LLM conversation that produced this row |
|
|
184
|
+
| `task_id` | `foreign_key ..., :tasks, on_delete: :set_null` | Links to the task that triggered this row |
|
|
185
|
+
| `contains_phi` | `TrueClass, default: false` | Row contains Protected Health Information |
|
|
186
|
+
| `contains_pii` | `TrueClass, default: false` | Row contains Personally Identifiable Information |
|
|
78
187
|
|
|
79
188
|
### Naming rules
|
|
80
189
|
|
|
@@ -87,6 +196,10 @@ All new tables in legion-data should follow this column convention. Required fie
|
|
|
87
196
|
- `074`-`076`: Apollo field width, task idempotency, extract step timings.
|
|
88
197
|
- `077`-`090`: LLM lifecycle ledger.
|
|
89
198
|
- `091`-`096`: portable identity companion tables.
|
|
199
|
+
- `097`: LLM dispatch fields (operation, correlation_id, provider_instance, dispatch_path).
|
|
200
|
+
- `098`-`099`: Legacy identity table drop + rename (portable_identity_* → identity_*).
|
|
201
|
+
- `100`-`102`: Apollo identity columns + access_scope + indexes.
|
|
202
|
+
- `103`-`114`: LLM table identity standardization (access_scope, identity_principal_id, identity_id, identity_canonical_name).
|
|
90
203
|
- Namespaced models: `Identity::*`, `Apollo::*`, `RBAC::*`, `LLM::*`.
|
|
91
204
|
|
|
92
205
|
## Boundaries
|
|
@@ -198,7 +198,12 @@ module Legion
|
|
|
198
198
|
connected: Legion::Settings[:data][:connected],
|
|
199
199
|
fallback_active: @fallback_active || false,
|
|
200
200
|
configured_adapter: Legion::Settings[:data][:adapter]&.to_sym || :sqlite,
|
|
201
|
-
sequel_alive: (begin
|
|
201
|
+
sequel_alive: (begin
|
|
202
|
+
!@sequel&.test_connection.nil?
|
|
203
|
+
rescue StandardError => e
|
|
204
|
+
log.debug("connection health check failed: #{e.message}")
|
|
205
|
+
false
|
|
206
|
+
end)
|
|
202
207
|
}
|
|
203
208
|
end
|
|
204
209
|
|
data/lib/legion/data/local.rb
CHANGED
|
@@ -48,9 +48,11 @@ module Legion
|
|
|
48
48
|
@connection.run('PRAGMA journal_mode=WAL')
|
|
49
49
|
@connection.run('PRAGMA busy_timeout=30000')
|
|
50
50
|
@connection.run('PRAGMA synchronous=NORMAL')
|
|
51
|
+
@connection.run('PRAGMA cache_size=-20000')
|
|
52
|
+
@connection.run('PRAGMA mmap_size=268435456')
|
|
51
53
|
@connected = true
|
|
52
54
|
run_migrations
|
|
53
|
-
log.info "Legion::Data::Local connected to #{db_file} (WAL mode, 30s busy_timeout)"
|
|
55
|
+
log.info "Legion::Data::Local connected to #{db_file} (WAL mode, 30s busy_timeout, 20MB cache, 256MB mmap)"
|
|
54
56
|
rescue StandardError => e
|
|
55
57
|
handle_exception(e, level: :error, handled: false, operation: :local_setup, database: db_file)
|
|
56
58
|
raise
|
|
@@ -99,7 +101,7 @@ module Legion
|
|
|
99
101
|
stats[:file_size] = File.size(@db_path) if @db_path && File.exist?(@db_path)
|
|
100
102
|
|
|
101
103
|
%w[page_size page_count freelist_count journal_mode
|
|
102
|
-
wal_autocheckpoint cache_size busy_timeout].each do |pragma|
|
|
104
|
+
wal_autocheckpoint cache_size busy_timeout mmap_size].each do |pragma|
|
|
103
105
|
val = begin
|
|
104
106
|
@connection.fetch("PRAGMA #{pragma}").single_value
|
|
105
107
|
rescue StandardError => e
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
alter_table(:llm_tool_calls) do
|
|
6
|
+
drop_index :identity_principal_id, name: :idx_tool_calls_identity_principal_id, if_exists: true
|
|
7
|
+
set_column_allow_null :message_inference_response_id
|
|
8
|
+
add_index :identity_principal_id, name: :idx_tool_calls_identity_principal_id,
|
|
9
|
+
where: Sequel.negate(identity_principal_id: nil)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
down do
|
|
14
|
+
alter_table(:llm_tool_calls) do
|
|
15
|
+
drop_index :identity_principal_id, name: :idx_tool_calls_identity_principal_id, if_exists: true
|
|
16
|
+
set_column_not_null :message_inference_response_id
|
|
17
|
+
add_index :identity_principal_id, name: :idx_tool_calls_identity_principal_id,
|
|
18
|
+
where: Sequel.negate(identity_principal_id: nil)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
alter_table(:llm_tool_calls) do
|
|
6
|
+
add_foreign_key :conversation_id, :llm_conversations, null: true, on_delete: :set_null, on_update: :cascade
|
|
7
|
+
add_index :conversation_id
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
down do
|
|
12
|
+
alter_table(:llm_tool_calls) do
|
|
13
|
+
drop_column :conversation_id
|
|
14
|
+
# On SQLite, drop_column triggers table recreation which silently destroys
|
|
15
|
+
# partial indexes. Recreate the one from migration 109.
|
|
16
|
+
add_index :identity_principal_id,
|
|
17
|
+
name: :idx_tool_calls_identity_principal_id,
|
|
18
|
+
where: Sequel.negate(identity_principal_id: nil),
|
|
19
|
+
if_not_exists: true
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The Great Convergence (part 1 of 2): add entity_type column to audit_records on all adapters.
|
|
4
|
+
# Migration 068 added this column on PostgreSQL only.
|
|
5
|
+
# Production is already at 117+, so this migration only runs on SQLite/MySQL
|
|
6
|
+
# deployments that missed it due to the postgres-only guard in migration 068.
|
|
7
|
+
|
|
8
|
+
Sequel.migration do
|
|
9
|
+
up do
|
|
10
|
+
return unless table_exists?(:audit_records)
|
|
11
|
+
|
|
12
|
+
existing = schema(:audit_records).map(&:first)
|
|
13
|
+
return if existing.include?(:entity_type)
|
|
14
|
+
|
|
15
|
+
alter_table(:audit_records) do
|
|
16
|
+
add_column :entity_type, String, size: 100, null: true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
add_index :audit_records, :entity_type, name: :idx_audit_records_entity_type, if_not_exists: true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
down do
|
|
23
|
+
return unless table_exists?(:audit_records)
|
|
24
|
+
|
|
25
|
+
alter_table(:audit_records) do
|
|
26
|
+
drop_column :entity_type if schema(:audit_records).any? { |col, _| col == :entity_type }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The Great Convergence (part 2): create apollo_relations, apollo_expertise,
|
|
4
|
+
# apollo_access_log, and apollo_operations on all adapters.
|
|
5
|
+
#
|
|
6
|
+
# Migration 012 (postgres-only) created apollo_relations, apollo_expertise,
|
|
7
|
+
# and apollo_access_log.
|
|
8
|
+
# Migration 047 (postgres-only) created apollo_operations.
|
|
9
|
+
# These tables were never created on SQLite/MySQL deployments.
|
|
10
|
+
|
|
11
|
+
Sequel.migration do
|
|
12
|
+
up do
|
|
13
|
+
# apollo_relations
|
|
14
|
+
unless table_exists?(:apollo_relations)
|
|
15
|
+
create_table(:apollo_relations) do
|
|
16
|
+
primary_key :id
|
|
17
|
+
String :from_entry_id, size: 36, null: false
|
|
18
|
+
String :to_entry_id, size: 36, null: false
|
|
19
|
+
String :relation_type, null: false, size: 50
|
|
20
|
+
Float :weight, default: 1.0
|
|
21
|
+
String :source_agent, size: 255
|
|
22
|
+
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
|
|
23
|
+
|
|
24
|
+
index :from_entry_id, name: :idx_apollo_rel_from
|
|
25
|
+
index :to_entry_id, name: :idx_apollo_rel_to
|
|
26
|
+
index :relation_type, name: :idx_apollo_rel_type
|
|
27
|
+
index %i[from_entry_id relation_type], name: :idx_apollo_rel_composite
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# apollo_expertise
|
|
32
|
+
unless table_exists?(:apollo_expertise)
|
|
33
|
+
create_table(:apollo_expertise) do
|
|
34
|
+
primary_key :id
|
|
35
|
+
String :agent_id, null: false, size: 255, index: { name: :idx_apollo_exp_agent }
|
|
36
|
+
String :domain, null: false, size: 255, index: { name: :idx_apollo_exp_domain }
|
|
37
|
+
Float :proficiency, default: 0.0
|
|
38
|
+
Integer :entry_count, default: 0
|
|
39
|
+
DateTime :last_active_at, default: Sequel::CURRENT_TIMESTAMP
|
|
40
|
+
|
|
41
|
+
index %i[agent_id domain], name: :idx_apollo_exp_composite
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# apollo_access_log
|
|
46
|
+
unless table_exists?(:apollo_access_log)
|
|
47
|
+
create_table(:apollo_access_log) do
|
|
48
|
+
primary_key :id
|
|
49
|
+
String :entry_id, size: 36, index: { name: :idx_apollo_access_entry }
|
|
50
|
+
String :agent_id, null: false, size: 255
|
|
51
|
+
String :action, null: false, size: 20
|
|
52
|
+
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# apollo_operations
|
|
57
|
+
unless table_exists?(:apollo_operations)
|
|
58
|
+
create_table(:apollo_operations) do
|
|
59
|
+
primary_key :id
|
|
60
|
+
String :operation, size: 50, null: false
|
|
61
|
+
String :actor, size: 255, null: false
|
|
62
|
+
String :target_type, size: 50
|
|
63
|
+
String :target_ids, text: true # serialized array; PG uses INTEGER[]
|
|
64
|
+
String :summary, text: true
|
|
65
|
+
String :detail, text: true, default: '{}' # serialized json; PG uses JSONB
|
|
66
|
+
String :old_state, text: true
|
|
67
|
+
String :new_state, text: true
|
|
68
|
+
String :reason, text: true
|
|
69
|
+
String :principal_id, size: 255
|
|
70
|
+
DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
|
|
71
|
+
|
|
72
|
+
index :created_at, name: :idx_apollo_ops_created
|
|
73
|
+
index :operation, name: :idx_apollo_ops_operation
|
|
74
|
+
index :actor, name: :idx_apollo_ops_actor
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
down do
|
|
80
|
+
drop_table :apollo_operations if table_exists?(:apollo_operations)
|
|
81
|
+
drop_table :apollo_access_log if table_exists?(:apollo_access_log)
|
|
82
|
+
drop_table :apollo_expertise if table_exists?(:apollo_expertise)
|
|
83
|
+
drop_table :apollo_relations if table_exists?(:apollo_relations)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The Great Convergence (part 3): add missing indexes on apollo_* tables.
|
|
4
|
+
#
|
|
5
|
+
# Migration 047 (postgres-only) created dozens of indexes on apollo_entries,
|
|
6
|
+
# apollo_relations, apollo_expertise, apollo_operations, and
|
|
7
|
+
# apollo_entries_archive. These were never created on SQLite/MySQL.
|
|
8
|
+
#
|
|
9
|
+
# Vector indexes (hnsw) and GIN indexes are postgres-specific and skipped.
|
|
10
|
+
#
|
|
11
|
+
# NOTE: Uses raw CREATE INDEX IF NOT EXISTS SQL because Sequel's add_index
|
|
12
|
+
# inside alter_table does not honor if_not_exists on SQLite (it triggers
|
|
13
|
+
# table recreation which fails if the index already exists).
|
|
14
|
+
|
|
15
|
+
Sequel.migration do
|
|
16
|
+
up do
|
|
17
|
+
return unless table_exists?(:apollo_entries)
|
|
18
|
+
|
|
19
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_submitted_by ON apollo_entries (submitted_by)'
|
|
20
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_submitted_from ON apollo_entries (submitted_from)'
|
|
21
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_status ON apollo_entries (status)'
|
|
22
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_confidence ON apollo_entries (confidence)'
|
|
23
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_created ON apollo_entries (created_at)'
|
|
24
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_updated ON apollo_entries (updated_at)'
|
|
25
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_domain ON apollo_entries (knowledge_domain)'
|
|
26
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_source_agent ON apollo_entries (source_agent)'
|
|
27
|
+
run "CREATE UNIQUE INDEX IF NOT EXISTS idx_apollo_content_hash ON apollo_entries (content_hash) WHERE status != 'archived'"
|
|
28
|
+
run "CREATE INDEX IF NOT EXISTS idx_apollo_active ON apollo_entries (id) WHERE status IN ('candidate', 'confirmed', 'disputed')"
|
|
29
|
+
run "CREATE INDEX IF NOT EXISTS idx_apollo_decay_target ON apollo_entries (updated_at) WHERE status != 'archived'"
|
|
30
|
+
run "CREATE INDEX IF NOT EXISTS idx_apollo_candidates ON apollo_entries (status, source_provider, source_channel) WHERE status = 'candidate'"
|
|
31
|
+
|
|
32
|
+
return unless table_exists?(:apollo_entries_archive)
|
|
33
|
+
|
|
34
|
+
run 'CREATE INDEX IF NOT EXISTS idx_archive_content_hash ON apollo_entries_archive (content_hash)'
|
|
35
|
+
run 'CREATE INDEX IF NOT EXISTS idx_archive_source_agent ON apollo_entries_archive (source_agent)'
|
|
36
|
+
run 'CREATE INDEX IF NOT EXISTS idx_archive_archived_at ON apollo_entries_archive (archived_at)'
|
|
37
|
+
|
|
38
|
+
return unless table_exists?(:apollo_relations)
|
|
39
|
+
|
|
40
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_rel_from ON apollo_relations (from_entry_id)'
|
|
41
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_rel_to ON apollo_relations (to_entry_id)'
|
|
42
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_rel_type ON apollo_relations (relation_type)'
|
|
43
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_rel_composite ON apollo_relations (from_entry_id, relation_type)'
|
|
44
|
+
|
|
45
|
+
return unless table_exists?(:apollo_expertise)
|
|
46
|
+
|
|
47
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_exp_agent ON apollo_expertise (agent_id)'
|
|
48
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_exp_domain ON apollo_expertise (domain)'
|
|
49
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_exp_composite ON apollo_expertise (agent_id, domain)'
|
|
50
|
+
|
|
51
|
+
return unless table_exists?(:apollo_operations)
|
|
52
|
+
|
|
53
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_ops_created ON apollo_operations (created_at)'
|
|
54
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_ops_operation ON apollo_operations (operation)'
|
|
55
|
+
run 'CREATE INDEX IF NOT EXISTS idx_apollo_ops_actor ON apollo_operations (actor)'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
down do
|
|
59
|
+
%w[
|
|
60
|
+
idx_apollo_submitted_by idx_apollo_submitted_from idx_apollo_status
|
|
61
|
+
idx_apollo_confidence idx_apollo_created idx_apollo_updated
|
|
62
|
+
idx_apollo_domain idx_apollo_source_agent idx_apollo_content_hash
|
|
63
|
+
idx_apollo_active idx_apollo_decay_target idx_apollo_candidates
|
|
64
|
+
idx_archive_content_hash idx_archive_source_agent idx_archive_archived_at
|
|
65
|
+
idx_apollo_rel_from idx_apollo_rel_to idx_apollo_rel_type
|
|
66
|
+
idx_apollo_rel_composite
|
|
67
|
+
idx_apollo_exp_agent idx_apollo_exp_domain idx_apollo_exp_composite
|
|
68
|
+
idx_apollo_ops_created idx_apollo_ops_operation idx_apollo_ops_actor
|
|
69
|
+
].each do |name|
|
|
70
|
+
run "DROP INDEX IF EXISTS #{name}"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Add cached_input_tokens and cache_creation_tokens to llm_message_inference_metrics.
|
|
4
|
+
# Tracks cache hit tokens (read from cache) and cache write tokens separately from
|
|
5
|
+
# standard input/output token counts.
|
|
6
|
+
#
|
|
7
|
+
# See: https://github.com/LegionIO/legion-data/issues/55
|
|
8
|
+
|
|
9
|
+
Sequel.migration do
|
|
10
|
+
up do
|
|
11
|
+
alter_table(:llm_message_inference_metrics) do
|
|
12
|
+
add_column :cached_input_tokens, Integer, null: false, default: 0
|
|
13
|
+
add_column :cache_creation_tokens, Integer, null: false, default: 0
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
down do
|
|
18
|
+
alter_table(:llm_message_inference_metrics) do
|
|
19
|
+
drop_column :cache_creation_tokens
|
|
20
|
+
drop_column :cached_input_tokens
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Migration 115 had a bug: it guarded the up block on the :definition column
|
|
4
|
+
# (added by migration 055) instead of :runtime_caller_class. This means on
|
|
5
|
+
# deployments where :definition existed but :runtime_caller_class did not,
|
|
6
|
+
# the columns were added correctly. But the guard was checking the wrong
|
|
7
|
+
# thing, and the down block has no guard at all.
|
|
8
|
+
#
|
|
9
|
+
# This migration ensures the columns exist on any deployment that might have
|
|
10
|
+
# skipped them due to the 115 bug.
|
|
11
|
+
|
|
12
|
+
Sequel.migration do
|
|
13
|
+
up do
|
|
14
|
+
if table_exists?(:llm_message_inference_requests)
|
|
15
|
+
cols = schema(:llm_message_inference_requests).map(&:first)
|
|
16
|
+
unless cols.include?(:runtime_caller_class) && cols.include?(:runtime_caller_client)
|
|
17
|
+
alter_table(:llm_message_inference_requests) do
|
|
18
|
+
add_column :runtime_caller_class, String, size: 255, null: true, index: true unless cols.include?(:runtime_caller_class)
|
|
19
|
+
add_column :runtime_caller_client, String, size: 255, null: true unless cols.include?(:runtime_caller_client)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
down do
|
|
26
|
+
if table_exists?(:llm_message_inference_requests)
|
|
27
|
+
cols = schema(:llm_message_inference_requests).map(&:first)
|
|
28
|
+
if cols.include?(:runtime_caller_class) || cols.include?(:runtime_caller_client)
|
|
29
|
+
alter_table(:llm_message_inference_requests) do
|
|
30
|
+
drop_column :runtime_caller_client if cols.include?(:runtime_caller_client)
|
|
31
|
+
drop_column :runtime_caller_class if cols.include?(:runtime_caller_class)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
change do
|
|
5
|
+
alter_table(:llm_tool_calls) do
|
|
6
|
+
add_column :tool_arguments_json, :text, null: true
|
|
7
|
+
add_column :tool_result_json, :text, null: true
|
|
8
|
+
add_column :tool_category, String, size: 64, null: true
|
|
9
|
+
add_column :data_handling_classification, String, size: 32, null: true
|
|
10
|
+
add_column :policy_decision, String, size: 32, null: true
|
|
11
|
+
add_column :requires_human_approval, TrueClass, null: true
|
|
12
|
+
add_index :tool_category, name: :idx_tool_calls_tool_category
|
|
13
|
+
add_index :data_handling_classification, name: :idx_tool_calls_data_handling_classification
|
|
14
|
+
add_index :policy_decision, name: :idx_tool_calls_policy_decision
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
change do
|
|
5
|
+
alter_table(:llm_tool_call_attempts) do
|
|
6
|
+
add_column :attempt_input_json, :text, null: true
|
|
7
|
+
add_column :attempt_output_json, :text, null: true
|
|
8
|
+
add_column :error_details_json, :text, null: true
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
next unless table_exists?(:llm_escalation_events)
|
|
6
|
+
|
|
7
|
+
alter_table(:llm_escalation_events) do
|
|
8
|
+
add_column :history_json, :text, null: true
|
|
9
|
+
add_column :outcome, String, size: 32, null: true
|
|
10
|
+
add_column :total_attempts, Integer, null: true
|
|
11
|
+
add_index :outcome, name: :idx_escalation_events_outcome
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
down do
|
|
16
|
+
next unless table_exists?(:llm_escalation_events)
|
|
17
|
+
|
|
18
|
+
alter_table(:llm_escalation_events) do
|
|
19
|
+
drop_index :outcome, name: :idx_escalation_events_outcome
|
|
20
|
+
drop_column :total_attempts
|
|
21
|
+
drop_column :outcome
|
|
22
|
+
drop_column :history_json
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# NOTE: response_content_hash already exists (migration 080) — skipped.
|
|
4
|
+
|
|
5
|
+
Sequel.migration do
|
|
6
|
+
change do
|
|
7
|
+
alter_table(:llm_message_inference_responses) do
|
|
8
|
+
add_column :route_attempts, Integer, null: true, default: 0
|
|
9
|
+
add_column :escalation_chain_ref, String, size: 128, null: true
|
|
10
|
+
add_index :escalation_chain_ref, name: :idx_inference_responses_escalation_chain_ref
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# NOTE: request_content_hash, curation_strategy, and tool_policy already exist
|
|
4
|
+
# (migration 079) — all skipped. Only parent_request_id is new.
|
|
5
|
+
|
|
6
|
+
Sequel.migration do
|
|
7
|
+
change do
|
|
8
|
+
alter_table(:llm_message_inference_requests) do
|
|
9
|
+
add_foreign_key :parent_request_id, :llm_message_inference_requests, null: true, on_delete: :set_null
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Sequel.migration do
|
|
4
|
+
up do
|
|
5
|
+
%i[apollo_access_log memory_traces memory_associations audit_log audit_records].each do |table|
|
|
6
|
+
next unless table_exists?(table)
|
|
7
|
+
|
|
8
|
+
cols = schema(table).map(&:first)
|
|
9
|
+
alter_table(table) do
|
|
10
|
+
add_column :identity_principal_id, Integer, null: true unless cols.include?(:identity_principal_id)
|
|
11
|
+
add_column :identity_id, Integer, null: true unless cols.include?(:identity_id)
|
|
12
|
+
add_column :identity_canonical_name, String, size: 255, null: true unless cols.include?(:identity_canonical_name)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
down do
|
|
18
|
+
%i[apollo_access_log memory_traces memory_associations audit_log audit_records].each do |table|
|
|
19
|
+
next unless table_exists?(table)
|
|
20
|
+
|
|
21
|
+
cols = schema(table).map(&:first)
|
|
22
|
+
alter_table(table) do
|
|
23
|
+
drop_column :identity_canonical_name if cols.include?(:identity_canonical_name)
|
|
24
|
+
drop_column :identity_id if cols.include?(:identity_id)
|
|
25
|
+
drop_column :identity_principal_id if cols.include?(:identity_principal_id)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -7,7 +7,8 @@ module Legion
|
|
|
7
7
|
module ModelHelpers
|
|
8
8
|
def self.table_available?(table_name)
|
|
9
9
|
Legion::Data::Connection.sequel&.table_exists?(table_name)
|
|
10
|
-
rescue StandardError
|
|
10
|
+
rescue StandardError => e
|
|
11
|
+
log.error("table availability check failed for #{table_name}: #{e.message}")
|
|
11
12
|
false
|
|
12
13
|
end
|
|
13
14
|
end
|
|
@@ -10,6 +10,7 @@ module Legion
|
|
|
10
10
|
include ModelHelpers
|
|
11
11
|
|
|
12
12
|
many_to_one :message_inference_response
|
|
13
|
+
many_to_one :conversation
|
|
13
14
|
many_to_one :requested_by_message, class: 'Legion::Data::Models::LLM::Message', key: :requested_by_message_id
|
|
14
15
|
many_to_one :result_message, class: 'Legion::Data::Models::LLM::Message', key: :result_message_id
|
|
15
16
|
one_to_many :tool_call_attempts
|
data/lib/legion/data/spool.rb
CHANGED
|
@@ -117,7 +117,8 @@ module Legion
|
|
|
117
117
|
|
|
118
118
|
def load_event_file(path, sub_namespace)
|
|
119
119
|
::JSON.parse(File.binread(path), symbolize_names: true)
|
|
120
|
-
rescue Errno::ENOENT
|
|
120
|
+
rescue Errno::ENOENT => e
|
|
121
|
+
log.debug("spool event file not found: #{path}: #{e.message}")
|
|
121
122
|
nil
|
|
122
123
|
rescue ::JSON::ParserError, EOFError, ArgumentError => e
|
|
123
124
|
quarantine_corrupt_file(path, sub_namespace, e)
|
data/lib/legion/data/version.rb
CHANGED
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.
|
|
4
|
+
version: 1.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -262,6 +262,19 @@ files:
|
|
|
262
262
|
- lib/legion/data/migrations/113_add_llm_security_events_identity_columns.rb
|
|
263
263
|
- lib/legion/data/migrations/114_add_llm_registry_events_identity_columns.rb
|
|
264
264
|
- lib/legion/data/migrations/115_add_runtime_caller_columns.rb
|
|
265
|
+
- lib/legion/data/migrations/116_make_tool_calls_response_id_nullable.rb
|
|
266
|
+
- lib/legion/data/migrations/117_add_conversation_id_to_llm_tool_calls.rb
|
|
267
|
+
- lib/legion/data/migrations/118_add_entity_type_to_audit_records.rb
|
|
268
|
+
- lib/legion/data/migrations/119_create_missing_apollo_tables.rb
|
|
269
|
+
- lib/legion/data/migrations/120_add_missing_apollo_indexes.rb
|
|
270
|
+
- lib/legion/data/migrations/121_add_cache_token_metrics.rb
|
|
271
|
+
- lib/legion/data/migrations/122_ensure_runtime_caller_columns.rb
|
|
272
|
+
- lib/legion/data/migrations/123_add_llm_tool_calls_audit_columns.rb
|
|
273
|
+
- lib/legion/data/migrations/124_add_llm_tool_call_attempts_audit_columns.rb
|
|
274
|
+
- lib/legion/data/migrations/125_add_llm_escalation_events_audit_columns.rb
|
|
275
|
+
- lib/legion/data/migrations/126_add_llm_message_inference_responses_audit_columns.rb
|
|
276
|
+
- lib/legion/data/migrations/127_add_llm_message_inference_requests_audit_columns.rb
|
|
277
|
+
- lib/legion/data/migrations/128_add_identity_columns_to_shared_tables.rb
|
|
265
278
|
- lib/legion/data/model.rb
|
|
266
279
|
- lib/legion/data/models/apollo/access_log.rb
|
|
267
280
|
- lib/legion/data/models/apollo/entries.rb
|