legion-data 1.7.3 → 1.8.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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.pre-commit-config.yaml +29 -0
  3. data/AGENTS.md +66 -13
  4. data/CHANGELOG.md +29 -0
  5. data/CLAUDE.md +44 -307
  6. data/README.md +119 -7
  7. data/lib/legion/data/connection.rb +3 -1
  8. data/lib/legion/data/migrations/077_create_llm_conversations.rb +32 -0
  9. data/lib/legion/data/migrations/078_create_llm_messages.rb +33 -0
  10. data/lib/legion/data/migrations/079_create_llm_message_inference_requests.rb +47 -0
  11. data/lib/legion/data/migrations/080_create_llm_message_inference_responses.rb +39 -0
  12. data/lib/legion/data/migrations/081_add_llm_message_inference_foreign_keys.rb +17 -0
  13. data/lib/legion/data/migrations/082_create_llm_route_attempts.rb +31 -0
  14. data/lib/legion/data/migrations/083_create_llm_message_inference_metrics.rb +36 -0
  15. data/lib/legion/data/migrations/084_create_llm_tool_calls.rb +32 -0
  16. data/lib/legion/data/migrations/085_add_llm_message_tool_call_foreign_key.rb +15 -0
  17. data/lib/legion/data/migrations/086_create_llm_tool_call_attempts.rb +30 -0
  18. data/lib/legion/data/migrations/087_create_llm_conversation_compactions.rb +31 -0
  19. data/lib/legion/data/migrations/088_create_llm_policy_evaluations.rb +33 -0
  20. data/lib/legion/data/migrations/089_create_llm_security_events.rb +33 -0
  21. data/lib/legion/data/migrations/090_create_llm_registry_events.rb +23 -0
  22. data/lib/legion/data/migrations/091_create_portable_identity_providers.rb +35 -0
  23. data/lib/legion/data/migrations/092_create_portable_identity_principals.rb +25 -0
  24. data/lib/legion/data/migrations/093_create_portable_identities.rb +31 -0
  25. data/lib/legion/data/migrations/094_create_portable_identity_groups.rb +21 -0
  26. data/lib/legion/data/migrations/095_create_portable_identity_group_memberships.rb +25 -0
  27. data/lib/legion/data/migrations/096_create_portable_identity_audit_log.rb +26 -0
  28. data/lib/legion/data/migrations/097_add_llm_dispatch_fields.rb +16 -0
  29. data/lib/legion/data/model.rb +11 -1
  30. data/lib/legion/data/models/apollo/access_log.rb +17 -0
  31. data/lib/legion/data/models/apollo/entries.rb +22 -0
  32. data/lib/legion/data/models/apollo/expertise.rb +16 -0
  33. data/lib/legion/data/models/apollo/model_helpers.rb +17 -0
  34. data/lib/legion/data/models/apollo/operation.rb +16 -0
  35. data/lib/legion/data/models/apollo/relation.rb +18 -0
  36. data/lib/legion/data/models/function.rb +1 -0
  37. data/lib/legion/data/models/identity/audit_log.rb +20 -0
  38. data/lib/legion/data/models/identity/group.rb +28 -0
  39. data/lib/legion/data/models/identity/group_memberships.rb +28 -0
  40. data/lib/legion/data/models/identity/identity.rb +24 -0
  41. data/lib/legion/data/models/identity/model_helpers.rb +86 -0
  42. data/lib/legion/data/models/identity/principal.rb +37 -0
  43. data/lib/legion/data/models/identity/providers.rb +34 -0
  44. data/lib/legion/data/models/identity.rb +8 -0
  45. data/lib/legion/data/models/identity_group.rb +13 -0
  46. data/lib/legion/data/models/identity_provider.rb +8 -0
  47. data/lib/legion/data/models/llm/conversation.rb +25 -0
  48. data/lib/legion/data/models/llm/conversation_compaction.rb +22 -0
  49. data/lib/legion/data/models/llm/message.rb +105 -0
  50. data/lib/legion/data/models/llm/message_inference_metric.rb +46 -0
  51. data/lib/legion/data/models/llm/message_inference_request.rb +80 -0
  52. data/lib/legion/data/models/llm/message_inference_response.rb +23 -0
  53. data/lib/legion/data/models/llm/model_helpers.rb +18 -0
  54. data/lib/legion/data/models/llm/policy_evaluation.rb +20 -0
  55. data/lib/legion/data/models/llm/registry_event.rb +15 -0
  56. data/lib/legion/data/models/llm/route_attempt.rb +18 -0
  57. data/lib/legion/data/models/llm/security_event.rb +66 -0
  58. data/lib/legion/data/models/llm/tool_call.rb +21 -0
  59. data/lib/legion/data/models/llm/tool_call_attempt.rb +18 -0
  60. data/lib/legion/data/models/node.rb +2 -1
  61. data/lib/legion/data/models/principal.rb +13 -0
  62. data/lib/legion/data/models/rbac/cross_team_grants.rb +25 -0
  63. data/lib/legion/data/models/rbac/model_helpers.rb +25 -0
  64. data/lib/legion/data/models/rbac/role_assignments.rb +25 -0
  65. data/lib/legion/data/models/rbac/runner_grants.rb +23 -0
  66. data/lib/legion/data/models/relationship.rb +1 -0
  67. data/lib/legion/data/models/runner.rb +24 -2
  68. data/lib/legion/data/models/task.rb +4 -0
  69. data/lib/legion/data/version.rb +1 -1
  70. data/scripts/pre-commit-rubocop.sh +16 -0
  71. metadata +54 -1
data/README.md CHANGED
@@ -1,11 +1,28 @@
1
1
  # legion-data
2
2
 
3
- Persistent database storage for the [LegionIO](https://github.com/LegionIO/LegionIO) async job engine and AI coding assistant platform. Provides database connectivity via the [Sequel ORM](https://sequel.jeremyevans.net/), automatic schema migrations (71 numbered migrations), Sequel models for the full LegionIO control plane, and a parallel local SQLite database for on-node agentic cognitive state.
3
+ Persistent database storage for the [LegionIO](https://github.com/LegionIO/LegionIO) async job engine and AI coding assistant platform. Provides database connectivity via the [Sequel ORM](https://sequel.jeremyevans.net/), automatic schema migrations (97 numbered migrations), Sequel models for the full LegionIO control plane, and a parallel local SQLite database for on-node agentic cognitive state.
4
4
 
5
- **Version**: 1.6.25 | **Ruby**: >= 3.4 | **License**: Apache-2.0
5
+ **Version**: 1.8.0 | **Ruby**: >= 3.4 | **License**: Apache-2.0
6
6
 
7
7
  ---
8
8
 
9
+ ## What It Owns
10
+
11
+ `legion-data` is the data contract for LegionIO. It owns database connectivity, migrations, model loading, and portable Sequel model definitions for shared platform state. HTTP routes, runtime orchestration, and extension behavior live in other LegionIO repos and call into these models.
12
+
13
+ Core responsibilities:
14
+
15
+ | Area | Tables and models |
16
+ |------|-------------------|
17
+ | Control plane | extensions, functions, runners, nodes, tasks, settings, workers, relationships, chains |
18
+ | Audit and governance | `audit_log`, `audit_records`, `governance_events`, archive manifests |
19
+ | Identity and RBAC | providers, principals, identities, groups, memberships, role grants, runner grants |
20
+ | LLM ledger | conversations, model-visible messages, inference requests/responses, routing, metrics, tool calls, policy/security events |
21
+ | Apollo knowledge | PostgreSQL `pgvector` knowledge entries, relations, expertise, access logs |
22
+ | Local state | on-node SQLite cognitive state, independent of the shared database |
23
+
24
+ The schema is portable by default across SQLite, MySQL, and PostgreSQL. PostgreSQL-only behavior is isolated to features that need PostgreSQL, such as Apollo vector columns.
25
+
9
26
  ## Supported Databases
10
27
 
11
28
  | Database | Adapter | Gem | Default |
@@ -51,18 +68,25 @@ Legion::Data (singleton module)
51
68
  │ ├── .adapter # Reads adapter from settings (:sqlite, :mysql2, :postgres)
52
69
  │ ├── .setup # Establish connection (dev_mode fallback to SQLite if unreachable)
53
70
  │ ├── .sequel # Raw Sequel::Database accessor
71
+ │ ├── .connection_info # Adapter, liveness, and fallback diagnostics
72
+ │ ├── .fallback_active? # True when dev fallback moved a network DB to SQLite
54
73
  │ ├── .stats # Pool metrics, tuning snapshot, adapter-specific DB stats
55
74
  │ └── .shutdown # Disconnect and close query file logger
56
75
 
57
- ├── Migration # Auto-migration system (71 numbered Sequel DSL migrations)
76
+ ├── Migration # Auto-migration system (97 numbered Sequel DSL migrations)
58
77
 
59
78
  ├── Model # Sequel model autoloader
60
79
  │ └── Models: Extension, Function, Runner, Node, Task, TaskLog, Setting,
61
80
  │ DigitalWorker, Relationship, AuditLog, AuditRecord, Chain,
62
81
  │ RbacRoleAssignment, RbacRunnerGrant, RbacCrossTeamGrant,
63
82
  │ IdentityProvider, Principal, Identity, IdentityGroup,
64
- │ IdentityGroupMembership,
65
- │ ApolloEntry, ApolloRelation, ApolloExpertise, ApolloAccessLog (PG only)
83
+ │ IdentityGroupMembership, IdentityAuditLog, ExtractStepTiming,
84
+ │ ApolloEntry, ApolloRelation, ApolloExpertise, ApolloAccessLog (PG only),
85
+ │ LLM::Conversation, LLM::Message, LLM::MessageInferenceRequest,
86
+ │ LLM::MessageInferenceResponse, LLM::RouteAttempt,
87
+ │ LLM::MessageInferenceMetric, LLM::ToolCall, LLM::ToolCallAttempt,
88
+ │ LLM::ConversationCompaction, LLM::PolicyEvaluation,
89
+ │ LLM::SecurityEvent, LLM::RegistryEvent
66
90
 
67
91
  ├── Local # Parallel local SQLite for agentic cognitive state
68
92
  │ ├── .setup # Lazy init — creates legionio_local.db on first access
@@ -117,10 +141,42 @@ Legion::Data.local.db_path # => "legionio_local.db"
117
141
  Legion::Data.connected? # => true
118
142
  Legion::Data.stats # => { shared: {...}, local: {...} }
119
143
 
144
+ # Inspect shared DB diagnostics, including dev fallback state
145
+ Legion::Data::Connection.connection_info
146
+ # => { adapter: :sqlite, connected: true, fallback_active: false, ... }
147
+
120
148
  # Shut down both connections
121
149
  Legion::Data.shutdown
122
150
  ```
123
151
 
152
+ ### Model Associations
153
+
154
+ Models use Sequel associations as the public object graph. Prefer association methods and association datasets over hand-written foreign-key lookups when the relationship is part of the schema contract.
155
+
156
+ ```ruby
157
+ task = Legion::Data::Model::Task.first(id: 42)
158
+ task.function # many_to_one :function
159
+ task.relationship # many_to_one :relationship
160
+ task.task_logs_dataset # further filter/order without losing the relationship
161
+
162
+ conversation = Legion::Data::Models::LLM::Conversation.first(uuid: conversation_uuid)
163
+ conversation.messages_dataset.order(:seq).all
164
+ conversation.security_incident_lineage
165
+ ```
166
+
167
+ Official LLM lifecycle data lives under `Legion::Data::Models::LLM`. `legion-llm` and `lex-llm-ledger` should use these models and the `llm_*` migration tables for conversations, model-visible messages, inference requests, responses, route attempts, metrics, tool calls, policy decisions, security events, and registry events. Legacy ledger-only tables are not the canonical schema.
168
+
169
+ Association rules used in this repo follow Sequel's own association model:
170
+
171
+ | Relationship | Use this Sequel association |
172
+ |--------------|-----------------------------|
173
+ | Current table has the foreign key | `many_to_one` |
174
+ | Associated table has the foreign key | `one_to_many` or `one_to_one` |
175
+ | Join table connects both sides | `many_to_many` |
176
+ | One associated record through a join table | `one_through_one` |
177
+
178
+ When Sequel cannot infer names from the schema, models must be explicit with `:class`, `:key`, `:primary_key`, `:join_table`, `:left_key`, and `:right_key`. Association names must not collide with real column names because Sequel creates methods with the association name.
179
+
124
180
  ### Local Database (Agentic Cognitive State)
125
181
 
126
182
  Extensions register their own migration directories and create models bound to the local connection:
@@ -314,6 +370,8 @@ When `dev_mode: true` and a network database is unreachable, the shared connecti
314
370
  { "data": { "dev_mode": true, "dev_fallback": true } }
315
371
  ```
316
372
 
373
+ Fallback is intentionally loud. `Connection.setup` logs the degraded mode at error level, `Connection.fallback_active?` returns `true`, and `Connection.connection_info` reports the configured adapter, actual adapter, connection state, and Sequel liveness. Data written during fallback is local-only SQLite data and will not appear in the configured network database after reconnect.
374
+
317
375
  ### HashiCorp Vault Integration
318
376
 
319
377
  When Vault is connected, credentials are fetched dynamically from `database/creds/legion`, overriding any static `creds` block.
@@ -377,6 +435,37 @@ Legion::Data.reload_static_cache
377
435
 
378
436
  Apollo models require PostgreSQL with the `pgvector` extension. They are skipped silently on SQLite and MySQL.
379
437
 
438
+ The `Legion::Data::Model::Identity::*`, `Apollo::*`, and `RBAC::*` namespaces provide cleaner Sequel model names for API-facing code while preserving the legacy flat model classes. Official LLM lifecycle models live under `Legion::Data::Models::LLM`.
439
+
440
+ ### Identity Namespace Models
441
+
442
+ | Model | Table | Description |
443
+ |-------|-------|-------------|
444
+ | `Identity::Provider` | `portable_identity_providers` | Portable provider records with integer primary keys and public UUIDs |
445
+ | `Identity::ProviderCapability` | `portable_identity_provider_capabilities` | Normalized provider capability declarations |
446
+ | `Identity::Principal` | `portable_identity_principals` | Human, service, worker, or system principals |
447
+ | `Identity::Identity` | `portable_identities` | Provider-bound identities for principals |
448
+ | `Identity::Group` | `portable_identity_groups` | Identity groups |
449
+ | `Identity::GroupMembership` | `portable_identity_group_memberships` | Principal and identity group membership rows |
450
+ | `Identity::AuditLog` | `portable_identity_audit_log` | Identity lifecycle and lookup audit events |
451
+
452
+ ### LLM Lifecycle Models
453
+
454
+ | Model | Table | Description |
455
+ |-------|-------|-------------|
456
+ | `LLM::Conversation` | `llm_conversations` | Conversation container tied to the base user identity |
457
+ | `LLM::Message` | `llm_messages` | Model-visible conversation transcript messages |
458
+ | `LLM::MessageInferenceRequest` | `llm_message_inference_requests` | Provider request assembled from message, context, tools, policy, and routing inputs |
459
+ | `LLM::MessageInferenceResponse` | `llm_message_inference_responses` | Provider/runtime response for one inference request |
460
+ | `LLM::RouteAttempt` | `llm_route_attempts` | Provider/model/runner routing attempts, including failures and escalations |
461
+ | `LLM::MessageInferenceMetric` | `llm_message_inference_metrics` | Token, latency, cost, and finance usage metrics for an inference pair |
462
+ | `LLM::ToolCall` | `llm_tool_calls` | Tool calls requested by an LLM provider response |
463
+ | `LLM::ToolCallAttempt` | `llm_tool_call_attempts` | Execution attempts, retries, failures, and results for provider-requested tool calls |
464
+ | `LLM::ConversationCompaction` | `llm_conversation_compactions` | Conversation-scoped compaction events |
465
+ | `LLM::PolicyEvaluation` | `llm_policy_evaluations` | Policy, classification, RBAC, and enforcement decisions for inference requests |
466
+ | `LLM::SecurityEvent` | `llm_security_events` | Security-relevant events tied to conversation, inference, response, or tool attempts |
467
+ | `LLM::RegistryEvent` | `llm_registry_events` | Provider/model registry availability and health events |
468
+
380
469
  ---
381
470
 
382
471
  ## Dependencies
@@ -396,7 +485,7 @@ Apollo models require PostgreSQL with the `pgvector` extension. They are skipped
396
485
 
397
486
  ## Migrations
398
487
 
399
- 76 numbered Sequel DSL migrations run automatically on startup (`auto_migrate: true`). Key milestones:
488
+ 97 numbered Sequel DSL migrations run automatically on startup (`auto_migrate: true`). Key milestones:
400
489
 
401
490
  | Range | What was added |
402
491
  |-------|---------------|
@@ -412,7 +501,11 @@ Apollo models require PostgreSQL with the `pgvector` extension. They are skipped
412
501
  | 050 | Critical indexes across 13 tables |
413
502
  | 058–067 | Audit records, chains, knowledge tiers, tool embedding cache, identity system (providers, principals, identities, groups) |
414
503
  | 068–071 | Entity type on audit records, principal on nodes, approval queue resume, engine on relationships |
415
- | 072–076 | Identity audit/multi-instance columns, Apollo identifier widening, task idempotency, Extract step timings |
504
+ | 072–073 | Identity audit log and multi-instance identity columns |
505
+ | 074–076 | Apollo field width fixes, task idempotency columns, and Extract step timing rows |
506
+ | 077–090 | Portable LLM lifecycle schema: conversations, messages, inference requests/responses, route attempts, inference metrics, provider-requested tool calls, compactions, policy/security, and registry events |
507
+ | 091–096 | Portable identity companion schema with integer primary keys, public UUIDs, provider capabilities, principals, identities, groups, memberships, and audit log |
508
+ | 097 | LLM dispatch identifiers for fleet operation, correlation, idempotency, provider instance, and dispatch path |
416
509
 
417
510
  Run migrations standalone:
418
511
 
@@ -420,6 +513,15 @@ Run migrations standalone:
420
513
  bundle exec legionio_migrate
421
514
  ```
422
515
 
516
+ Migration rules:
517
+
518
+ - Do not edit published migrations.
519
+ - Do not guard migrations with `create_table?`, `table_exists?`, `if_not_exists`, or similar conditional schema logic.
520
+ - Add new migrations in the next available number and keep domains split by dependency and rollback risk.
521
+ - Use portable Sequel DSL unless a feature truly requires adapter-specific behavior.
522
+ - Prefer integer `id` primary keys for joins plus public `uuid` columns for APIs, logs, and external references.
523
+ - Avoid JSON columns unless the shape is genuinely provider-specific or dynamic evidence.
524
+
423
525
  ---
424
526
 
425
527
  ## CLI Executable
@@ -449,6 +551,7 @@ bundle exec legionio_migrate
449
551
  11. Financial logging for UAIS cost recovery
450
552
  12. Global tool embedding cache (L4 tier for `Legion::Tools::EmbeddingCache`)
451
553
  13. Unified identity system (providers, principals, identities, groups)
554
+ 14. LLM lifecycle ledger for audit, finance metrics, routing reconstruction, tool calls, and security incident lineage
452
555
 
453
556
  ---
454
557
 
@@ -462,6 +565,15 @@ bundle exec rspec # all tests must pass
462
565
  bundle exec rubocop -A # zero offenses expected
463
566
  ```
464
567
 
568
+ This repo also includes a pre-commit configuration:
569
+
570
+ ```bash
571
+ pre-commit install
572
+ pre-commit run --all-files
573
+ ```
574
+
575
+ The local RuboCop hook auto-corrects staged Ruby files when RuboCop is available and fails the commit when RuboCop reports real offenses. The Ruby syntax hook checks every staged Ruby file.
576
+
465
577
  Follow the [LegionIO contribution guide](https://github.com/LegionIO/.github/blob/main/CONTRIBUTING.md). Open a PR against `main`.
466
578
 
467
579
  ---
@@ -161,6 +161,7 @@ module Legion
161
161
  end
162
162
 
163
163
  def setup
164
+ @adapter = Legion::Settings[:data][:adapter]&.to_sym || :sqlite
164
165
  opts = sequel_opts
165
166
  log.info("Legion::Data::Connection setup adapter=#{adapter}")
166
167
  @fallback_active = false
@@ -173,7 +174,7 @@ module Legion
173
174
  rescue StandardError => e
174
175
  raise unless dev_fallback?
175
176
 
176
- log.error("Legion::Data FALLING BACK TO SQLITE — #{attempted_adapter} connection failed: #{e.message}")
177
+ log.error("Legion::Data FALLING BACK TO SQLITE — #{attempted_adapter} network DB connection failed: #{e.message}")
177
178
  log.error("Legion::Data WARNING: Data written to SQLite will NOT be visible when #{attempted_adapter} reconnects. " \
178
179
  'Apollo knowledge, audit logs, and other DB-backed services will use a local-only store.')
179
180
  handle_exception(e, level: :error, handled: true, operation: :shared_connect, fallback: :sqlite)
@@ -261,6 +262,7 @@ module Legion
261
262
  @sequel&.disconnect
262
263
  @query_file_logger&.close
263
264
  @query_file_logger = nil
265
+ @fallback_active = false
264
266
  Legion::Settings[:data][:connected] = false
265
267
  log.info 'Legion::Data connection closed'
266
268
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:llm_conversations) do
6
+ primary_key :id
7
+ String :uuid, size: 36, null: false, unique: true
8
+ Integer :principal_id
9
+ Integer :identity_id
10
+ String :title, size: 255
11
+ String :status, size: 32, null: false, default: 'active'
12
+ String :system_prompt_key, size: 255
13
+ String :system_prompt_hash, size: 128
14
+ String :classification_level, size: 64
15
+ TrueClass :contains_phi, null: false, default: false
16
+ TrueClass :contains_pii, null: false, default: false
17
+ String :retention_policy, size: 64, null: false, default: 'default'
18
+ DateTime :expires_at
19
+ DateTime :recorded_at
20
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
21
+ DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
22
+ DateTime :updated_at, null: false, default: Sequel::CURRENT_TIMESTAMP
23
+
24
+ index :uuid
25
+ index :principal_id
26
+ index :identity_id
27
+ index :status
28
+ index :retention_policy
29
+ index :expires_at
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:llm_messages) do
6
+ primary_key :id
7
+ String :uuid, size: 36, null: false, unique: true
8
+ foreign_key :conversation_id, :llm_conversations, null: false, on_delete: :cascade
9
+ foreign_key :parent_message_id, :llm_messages, null: true, on_delete: :set_null
10
+ Integer :message_inference_request_id
11
+ Integer :message_inference_response_id
12
+ Integer :tool_call_id
13
+ Integer :seq, null: false
14
+ String :role, size: 32, null: false
15
+ String :content_type, size: 64, null: false, default: 'text'
16
+ String :content, text: true
17
+ Integer :input_tokens, null: false, default: 0
18
+ Integer :output_tokens, null: false, default: 0
19
+ DateTime :created_at, null: false, default: Sequel::CURRENT_TIMESTAMP
20
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
21
+
22
+ unique %i[conversation_id seq]
23
+ index :uuid
24
+ index :conversation_id
25
+ index :parent_message_id
26
+ index :message_inference_request_id
27
+ index :message_inference_response_id
28
+ index :tool_call_id
29
+ index %i[conversation_id role]
30
+ index :created_at
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:llm_message_inference_requests) do
6
+ primary_key :id
7
+ String :uuid, size: 36, null: false, unique: true
8
+ foreign_key :conversation_id, :llm_conversations, null: false, on_delete: :cascade
9
+ foreign_key :latest_message_id, :llm_messages, null: true, on_delete: :set_null
10
+ Integer :caller_principal_id
11
+ Integer :caller_identity_id
12
+ String :runtime_caller_type, size: 64
13
+ String :request_ref, size: 128
14
+ String :correlation_ref, size: 128
15
+ String :exchange_ref, size: 128
16
+ String :request_type, size: 64, null: false, default: 'chat'
17
+ String :status, size: 64, null: false, default: 'created'
18
+ Integer :context_message_count, null: false, default: 0
19
+ Integer :context_tokens, null: false, default: 0
20
+ Integer :token_budget, null: false, default: 0
21
+ String :curation_strategy, size: 128
22
+ Integer :injected_tool_count, null: false, default: 0
23
+ String :tool_policy, size: 128
24
+ String :request_capture_mode, size: 64, null: false, default: 'metadata_only'
25
+ String :request_content_hash, size: 128
26
+ String :request_json, text: true
27
+ String :classification_level, size: 64
28
+ String :rbac_decision, size: 64
29
+ String :cost_center, size: 128
30
+ String :budget_key, size: 128
31
+ DateTime :requested_at
32
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
33
+
34
+ index :uuid
35
+ index :conversation_id
36
+ index :latest_message_id
37
+ index :caller_principal_id
38
+ index :caller_identity_id
39
+ index :request_ref
40
+ index :correlation_ref
41
+ index :exchange_ref
42
+ index :status
43
+ index %i[cost_center requested_at]
44
+ index :requested_at
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:llm_message_inference_responses) do
6
+ primary_key :id
7
+ String :uuid, size: 36, null: false, unique: true
8
+ foreign_key :message_inference_request_id, :llm_message_inference_requests, null: false, on_delete: :cascade
9
+ foreign_key :response_message_id, :llm_messages, null: true, on_delete: :set_null
10
+ String :provider, size: 128
11
+ String :model_key, size: 255
12
+ String :tier, size: 64
13
+ String :runner_ref, size: 128
14
+ String :provider_response_ref, size: 255
15
+ String :status, size: 64, null: false, default: 'created'
16
+ String :finish_reason, size: 128
17
+ String :error_category, size: 128
18
+ String :error_code, size: 128
19
+ String :error_message, text: true
20
+ Integer :latency_ms, null: false, default: 0
21
+ Integer :wall_clock_ms, null: false, default: 0
22
+ String :response_capture_mode, size: 64, null: false, default: 'metadata_only'
23
+ String :response_content_hash, size: 128
24
+ String :response_json, text: true
25
+ String :response_thinking_json, text: true
26
+ DateTime :responded_at
27
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
28
+
29
+ index :uuid
30
+ index :message_inference_request_id
31
+ index :response_message_id
32
+ index %i[provider model_key]
33
+ index :runner_ref
34
+ index :provider_response_ref
35
+ index :status
36
+ index :responded_at
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ alter_table(:llm_messages) do
6
+ add_foreign_key [:message_inference_request_id], :llm_message_inference_requests, key: :id, on_delete: :set_null
7
+ add_foreign_key [:message_inference_response_id], :llm_message_inference_responses, key: :id, on_delete: :set_null
8
+ end
9
+ end
10
+
11
+ down do
12
+ alter_table(:llm_messages) do
13
+ drop_foreign_key [:message_inference_response_id]
14
+ drop_foreign_key [:message_inference_request_id]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:llm_route_attempts) do
6
+ primary_key :id
7
+ String :uuid, size: 36, null: false, unique: true
8
+ foreign_key :message_inference_request_id, :llm_message_inference_requests, null: false, on_delete: :cascade
9
+ foreign_key :message_inference_response_id, :llm_message_inference_responses, null: true, on_delete: :set_null
10
+ Integer :attempt_no, null: false
11
+ String :provider, size: 128
12
+ String :model_key, size: 255
13
+ String :tier, size: 64
14
+ String :route_target, size: 255
15
+ String :status, size: 64, null: false
16
+ String :failure_reason, text: true
17
+ Integer :latency_ms, null: false, default: 0
18
+ DateTime :started_at
19
+ DateTime :ended_at
20
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
21
+
22
+ unique %i[message_inference_request_id attempt_no]
23
+ index :uuid
24
+ index :message_inference_request_id
25
+ index :message_inference_response_id
26
+ index %i[provider model_key]
27
+ index :status
28
+ index :started_at
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:llm_message_inference_metrics) do
6
+ primary_key :id
7
+ String :uuid, size: 36, null: false, unique: true
8
+ foreign_key :message_inference_request_id, :llm_message_inference_requests, null: false, on_delete: :cascade
9
+ foreign_key :message_inference_response_id, :llm_message_inference_responses, null: true, on_delete: :set_null
10
+ String :provider, size: 128
11
+ String :model_key, size: 255
12
+ String :tier, size: 64
13
+ Integer :input_tokens, null: false, default: 0
14
+ Integer :output_tokens, null: false, default: 0
15
+ Integer :thinking_tokens, null: false, default: 0
16
+ Integer :total_tokens, null: false, default: 0
17
+ Integer :latency_ms, null: false, default: 0
18
+ Integer :wall_clock_ms, null: false, default: 0
19
+ BigDecimal :cost_usd, size: [20, 8], null: false, default: 0
20
+ String :currency, size: 3, null: false, default: 'USD'
21
+ String :cost_center, size: 128
22
+ String :budget_key, size: 128
23
+ DateTime :recorded_at
24
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
25
+
26
+ index :uuid
27
+ index :message_inference_request_id
28
+ index :message_inference_response_id
29
+ index %i[provider model_key]
30
+ index :cost_center
31
+ index :budget_key
32
+ index :recorded_at
33
+ index %i[cost_center recorded_at]
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:llm_tool_calls) do
6
+ primary_key :id
7
+ String :uuid, size: 36, null: false, unique: true
8
+ foreign_key :message_inference_response_id, :llm_message_inference_responses, null: false, on_delete: :cascade
9
+ foreign_key :requested_by_message_id, :llm_messages, null: true, on_delete: :set_null
10
+ foreign_key :result_message_id, :llm_messages, null: true, on_delete: :set_null
11
+ Integer :tool_call_index, null: false, default: 0
12
+ String :provider_tool_call_ref, size: 255
13
+ String :tool_name, size: 255, null: false
14
+ String :tool_source_type, size: 128
15
+ String :tool_source_server, size: 255
16
+ String :status, size: 64, null: false, default: 'requested'
17
+ DateTime :requested_at
18
+ DateTime :completed_at
19
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
20
+
21
+ unique %i[message_inference_response_id tool_call_index]
22
+ index :uuid
23
+ index :message_inference_response_id
24
+ index :requested_by_message_id
25
+ index :result_message_id
26
+ index :provider_tool_call_ref
27
+ index :tool_name
28
+ index :status
29
+ index :requested_at
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ alter_table(:llm_messages) do
6
+ add_foreign_key [:tool_call_id], :llm_tool_calls, key: :id, on_delete: :set_null
7
+ end
8
+ end
9
+
10
+ down do
11
+ alter_table(:llm_messages) do
12
+ drop_foreign_key [:tool_call_id]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:llm_tool_call_attempts) do
6
+ primary_key :id
7
+ String :uuid, size: 36, null: false, unique: true
8
+ foreign_key :tool_call_id, :llm_tool_calls, null: false, on_delete: :cascade
9
+ Integer :attempt_no, null: false
10
+ String :runner_ref, size: 128
11
+ String :status, size: 64, null: false
12
+ String :error_category, size: 128
13
+ String :error_code, size: 128
14
+ String :error_message, text: true
15
+ Integer :duration_ms, null: false, default: 0
16
+ String :arguments_ref, size: 255
17
+ String :result_ref, size: 255
18
+ DateTime :started_at
19
+ DateTime :ended_at
20
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
21
+
22
+ unique %i[tool_call_id attempt_no]
23
+ index :uuid
24
+ index :tool_call_id
25
+ index :runner_ref
26
+ index :status
27
+ index :started_at
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:llm_conversation_compactions) do
6
+ primary_key :id
7
+ String :uuid, size: 36, null: false, unique: true
8
+ foreign_key :conversation_id, :llm_conversations, null: false, on_delete: :cascade
9
+ foreign_key :triggered_by_message_inference_request_id, :llm_message_inference_requests, null: true,
10
+ on_delete: :set_null
11
+ foreign_key :replaces_message_from_id, :llm_messages, null: true, on_delete: :set_null
12
+ foreign_key :replaces_message_to_id, :llm_messages, null: true, on_delete: :set_null
13
+ String :strategy, size: 128
14
+ String :status, size: 64, null: false, default: 'created'
15
+ Integer :source_message_count, null: false, default: 0
16
+ Integer :source_token_count, null: false, default: 0
17
+ Integer :compacted_token_count, null: false, default: 0
18
+ String :content_hash, size: 128
19
+ String :summary, text: true
20
+ String :error_message, text: true
21
+ DateTime :compacted_at
22
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
23
+
24
+ index :uuid
25
+ index :conversation_id
26
+ index :triggered_by_message_inference_request_id
27
+ index :status
28
+ index :compacted_at
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:llm_policy_evaluations) do
6
+ primary_key :id
7
+ String :uuid, size: 36, null: false, unique: true
8
+ foreign_key :conversation_id, :llm_conversations, null: true, on_delete: :set_null
9
+ foreign_key :message_inference_request_id, :llm_message_inference_requests, null: true, on_delete: :set_null
10
+ foreign_key :message_inference_response_id, :llm_message_inference_responses, null: true, on_delete: :set_null
11
+ String :policy_key, size: 128, null: false
12
+ String :policy_version, size: 64
13
+ String :evaluation_type, size: 64, null: false
14
+ String :decision, size: 64, null: false
15
+ String :enforcement_action, size: 64
16
+ String :classification_level, size: 64
17
+ TrueClass :contains_phi, null: false, default: false
18
+ TrueClass :contains_pii, null: false, default: false
19
+ String :reason_code, size: 128
20
+ String :reason, text: true
21
+ DateTime :evaluated_at
22
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
23
+
24
+ index :uuid
25
+ index :conversation_id
26
+ index :message_inference_request_id
27
+ index :message_inference_response_id
28
+ index :policy_key
29
+ index :decision
30
+ index :evaluated_at
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ create_table(:llm_security_events) do
6
+ primary_key :id
7
+ String :uuid, size: 36, null: false, unique: true
8
+ foreign_key :conversation_id, :llm_conversations, null: true, on_delete: :set_null
9
+ foreign_key :message_inference_request_id, :llm_message_inference_requests, null: true, on_delete: :set_null
10
+ foreign_key :message_inference_response_id, :llm_message_inference_responses, null: true, on_delete: :set_null
11
+ foreign_key :tool_call_id, :llm_tool_calls, null: true, on_delete: :set_null
12
+ foreign_key :tool_call_attempt_id, :llm_tool_call_attempts, null: true, on_delete: :set_null
13
+ foreign_key :policy_evaluation_id, :llm_policy_evaluations, null: true, on_delete: :set_null
14
+ String :event_type, size: 128, null: false
15
+ String :severity, size: 32, null: false, default: 'info'
16
+ String :status, size: 64, null: false, default: 'open'
17
+ String :description, text: true
18
+ DateTime :detected_at
19
+ DateTime :inserted_at, null: false, default: Sequel::CURRENT_TIMESTAMP
20
+
21
+ index :uuid
22
+ index :conversation_id
23
+ index :message_inference_request_id
24
+ index :message_inference_response_id
25
+ index :tool_call_id
26
+ index :tool_call_attempt_id
27
+ index :policy_evaluation_id
28
+ index :event_type
29
+ index :severity
30
+ index :detected_at
31
+ end
32
+ end
33
+ end