legion-data 1.3.8 → 1.4.2

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: 8b4deeb282c6d4c359d6fa8d46de2f18f3107746536227171ae4b47b5f80d7b0
4
- data.tar.gz: 4f445caf46e5258694170c43ea429a3c6de4f25bd25f013617cf987d335077ad
3
+ metadata.gz: eaf37e68acae5015ef50b01e6a5c7a4806e81b80f5cf74f3c71b72e6799f0bd4
4
+ data.tar.gz: 81b55083c6e8d59403b51a838935a4146b1d3f39eca8316dfd82ee4d2f6a0b05
5
5
  SHA512:
6
- metadata.gz: '06930c4164c05d58320e1e5a35b9be15b76e647ba06826b05df8bdd4db156283e232a2109f930e31f78cb892f4cd25324ac941810a377760ee6956577c64fa97'
7
- data.tar.gz: 1cbfe73b30462c6b3807d15a494e48b7c4cb986e93b472a1357f722f5e13fb597ff2c70fae0f31911f1ae4711855d91a078c16fbf048835d5483aa849da0d252
6
+ metadata.gz: 4a24d0a86df7d44669ff38d34667c4f0c63036f877d1453df2e1418670b8a8c59ad803f9c245f5895094465d994253e1b4eced1007f0724cd31f53c4ae804d37
7
+ data.tar.gz: 830b7fc8203eb8a339a5ad84d334e56dabf0e6e02084be99a3b27a3067b104d7b5ca973437f3f8577050983c0126a302903624bdcf6a2916e267a1e6a8558754
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Legion::Data Changelog
2
2
 
3
+ ## v1.4.2
4
+
5
+ ### Fixed
6
+ - Migration 015: use `create_table?` instead of `create_table` for idempotent RBAC table creation
7
+
8
+ ## v1.4.1
9
+
10
+ ### Added
11
+ - Migration 025: tenants table (tenant_id, name, status, quotas, token limits)
12
+
13
+ ## v1.4.0
14
+
15
+ ### Added
16
+ - `Legion::Data::Vector`: reusable pgvector helpers (available?, cosine_search, l2_search, ensure_extension!)
17
+ - `Legion::Data::StorageTiers`: hot/warm/cold archival lifecycle (archive_to_warm, export_to_cold, stats)
18
+ - Migration 022: memory_traces table with optional pgvector embedding column (1536-dim, HNSW index)
19
+ - Migration 023: data_archive table for generic storage tier archival
20
+ - Migration 024: tenant_id partition columns on tasks, digital_workers, audit_log, memory_traces
21
+
3
22
  ## v1.3.8
4
23
 
5
24
  ### Added
@@ -2,7 +2,7 @@
2
2
 
3
3
  Sequel.migration do
4
4
  up do
5
- create_table(:rbac_role_assignments) do
5
+ create_table?(:rbac_role_assignments) do
6
6
  primary_key :id
7
7
  String :principal_type, null: false, size: 10
8
8
  String :principal_id, null: false, size: 255
@@ -16,7 +16,7 @@ Sequel.migration do
16
16
  index :team
17
17
  end
18
18
 
19
- create_table(:rbac_runner_grants) do
19
+ create_table?(:rbac_runner_grants) do
20
20
  primary_key :id
21
21
  String :team, null: false, size: 255
22
22
  String :runner_pattern, null: false, size: 500
@@ -27,7 +27,7 @@ Sequel.migration do
27
27
  index :team
28
28
  end
29
29
 
30
- create_table(:rbac_cross_team_grants) do
30
+ create_table?(:rbac_cross_team_grants) do
31
31
  primary_key :id
32
32
  String :source_team, null: false, size: 255
33
33
  String :target_team, null: false, size: 255
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ create_table(:memory_traces) do
6
+ primary_key :id
7
+ String :agent_id, null: false, size: 64, index: true
8
+ String :trace_type, null: false, size: 32
9
+ String :content, text: true, null: false
10
+ Float :significance, default: 0.5
11
+ Float :confidence, default: 1.0
12
+ String :associations, text: true
13
+ String :metadata, text: true
14
+ DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
15
+ DateTime :accessed_at
16
+ DateTime :decayed_at
17
+ index %i[agent_id trace_type]
18
+ end
19
+
20
+ next unless adapter_scheme == :postgres
21
+
22
+ run 'ALTER TABLE memory_traces ADD COLUMN IF NOT EXISTS embedding vector(1536)'
23
+ run 'CREATE INDEX IF NOT EXISTS idx_memory_traces_embedding ON memory_traces USING hnsw (embedding vector_cosine_ops)'
24
+ end
25
+
26
+ down do
27
+ drop_table?(:memory_traces)
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ create_table(:data_archive) do
6
+ primary_key :id
7
+ String :source_table, null: false, size: 64, index: true
8
+ Integer :source_id, null: false
9
+ String :data, text: true, null: false
10
+ Integer :tier, default: 1
11
+ DateTime :archived_at, default: Sequel::CURRENT_TIMESTAMP
12
+ index %i[source_table source_id]
13
+ index :tier
14
+ end
15
+ end
16
+
17
+ down do
18
+ drop_table?(:data_archive)
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ %i[tasks digital_workers audit_log memory_traces].each do |table|
6
+ next unless table_exists?(table)
7
+ next if schema(table).any? { |col, _| col == :tenant_id }
8
+
9
+ alter_table(table) do
10
+ add_column :tenant_id, String, size: 64
11
+ add_index :tenant_id
12
+ end
13
+ end
14
+ end
15
+
16
+ down do
17
+ %i[tasks digital_workers audit_log memory_traces].each do |table|
18
+ next unless table_exists?(table)
19
+ next unless schema(table).any? { |col, _| col == :tenant_id }
20
+
21
+ alter_table(table) do
22
+ drop_index :tenant_id
23
+ drop_column :tenant_id
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ up do
5
+ create_table(:tenants) do
6
+ primary_key :id
7
+ String :tenant_id, null: false, unique: true, size: 100
8
+ String :name, size: 255
9
+ String :status, default: 'active', size: 20
10
+ Integer :max_workers, default: 10
11
+ Integer :max_queue_depth, default: 10_000
12
+ Float :monthly_token_limit
13
+ Float :daily_token_limit
14
+ DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
15
+ DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
16
+ index :status
17
+ end
18
+ end
19
+
20
+ down do
21
+ drop_table?(:tenants)
22
+ end
23
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Data
5
+ module StorageTiers
6
+ TIERS = { hot: 0, warm: 1, cold: 2 }.freeze
7
+
8
+ class << self
9
+ def archive_to_warm(table:, age_days: 90, batch_size: 1000)
10
+ return { archived: 0, reason: 'no_connection' } unless Legion::Data.connection
11
+ return { archived: 0, reason: 'no_archive_table' } unless Legion::Data.connection.table_exists?(:data_archive)
12
+
13
+ cutoff = Time.now - (age_days * 86_400)
14
+ records = Legion::Data.connection[table].where { created_at < cutoff }.limit(batch_size).all
15
+ return { archived: 0 } if records.empty?
16
+
17
+ Legion::Data.connection.transaction do
18
+ records.each do |record|
19
+ Legion::Data.connection[:data_archive].insert(
20
+ source_table: table.to_s, source_id: record[:id],
21
+ data: Legion::JSON.dump(record),
22
+ tier: TIERS[:warm],
23
+ archived_at: Time.now.utc
24
+ )
25
+ end
26
+
27
+ ids = records.map { |r| r[:id] }
28
+ Legion::Data.connection[table].where(id: ids).delete
29
+ end
30
+
31
+ { archived: records.size, table: table.to_s }
32
+ end
33
+
34
+ def export_to_cold(age_days: 365, batch_size: 5000)
35
+ return { exported: 0 } unless Legion::Data.connection&.table_exists?(:data_archive)
36
+
37
+ cutoff = Time.now - (age_days * 86_400)
38
+ records = Legion::Data.connection[:data_archive]
39
+ .where(tier: TIERS[:warm])
40
+ .where { archived_at < cutoff }
41
+ .limit(batch_size).all
42
+ return { exported: 0 } if records.empty?
43
+
44
+ ids = records.map { |r| r[:id] }
45
+ Legion::Data.connection[:data_archive].where(id: ids).update(tier: TIERS[:cold])
46
+ { exported: records.size, data: records }
47
+ end
48
+
49
+ def stats
50
+ return {} unless Legion::Data.connection&.table_exists?(:data_archive)
51
+
52
+ { warm: count_tier(:warm), cold: count_tier(:cold) }
53
+ end
54
+
55
+ private
56
+
57
+ def count_tier(tier)
58
+ Legion::Data.connection[:data_archive].where(tier: TIERS[tier]).count
59
+ rescue StandardError
60
+ 0
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Data
5
+ module Vector
6
+ class << self
7
+ def available?
8
+ return false unless Legion::Data.connection
9
+ return false unless Legion::Data.connection.adapter_scheme == :postgres
10
+
11
+ Legion::Data.connection.fetch("SELECT 1 FROM pg_extension WHERE extname = 'vector'").any?
12
+ rescue StandardError
13
+ false
14
+ end
15
+
16
+ def ensure_extension!
17
+ return false unless Legion::Data.connection&.adapter_scheme == :postgres
18
+
19
+ Legion::Data.connection.run('CREATE EXTENSION IF NOT EXISTS vector')
20
+ true
21
+ rescue StandardError => e
22
+ Legion::Logging.warn("pgvector extension creation failed: #{e.message}") if defined?(Legion::Logging)
23
+ false
24
+ end
25
+
26
+ def cosine_search(table:, column:, query_vector:, limit: 10, min_similarity: 0.0)
27
+ return [] unless available?
28
+
29
+ vec_literal = vector_literal(query_vector)
30
+ ds = Legion::Data.connection[table]
31
+ .select_all
32
+ .select_append(Sequel.lit("1 - (#{column} <=> ?)", vec_literal).as(:similarity))
33
+ .order(Sequel.lit("#{column} <=> ?", vec_literal))
34
+ .limit(limit)
35
+
36
+ ds = ds.where(Sequel.lit("1 - (#{column} <=> ?) >= ?", vec_literal, min_similarity)) if min_similarity.positive?
37
+ ds.all
38
+ end
39
+
40
+ def l2_search(table:, column:, query_vector:, limit: 10)
41
+ return [] unless available?
42
+
43
+ vec_literal = vector_literal(query_vector)
44
+ Legion::Data.connection[table]
45
+ .select_all
46
+ .select_append(Sequel.lit("#{column} <-> ?", vec_literal).as(:distance))
47
+ .order(Sequel.lit("#{column} <-> ?", vec_literal))
48
+ .limit(limit)
49
+ .all
50
+ end
51
+
52
+ private
53
+
54
+ def vector_literal(query_vector)
55
+ "[#{query_vector.join(',')}]"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Data
5
- VERSION = '1.3.8'
5
+ VERSION = '1.4.2'
6
6
  end
7
7
  end
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.3.8
4
+ version: 1.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -117,6 +117,10 @@ files:
117
117
  - lib/legion/data/migrations/019_add_audit_hash_chain.rb
118
118
  - lib/legion/data/migrations/020_add_webhooks.rb
119
119
  - lib/legion/data/migrations/021_add_archive_tables.rb
120
+ - lib/legion/data/migrations/022_add_memory_traces.rb
121
+ - lib/legion/data/migrations/023_add_data_archive.rb
122
+ - lib/legion/data/migrations/024_add_tenant_partition_columns.rb
123
+ - lib/legion/data/migrations/025_add_tenants_table.rb
120
124
  - lib/legion/data/model.rb
121
125
  - lib/legion/data/models/apollo_access_log.rb
122
126
  - lib/legion/data/models/apollo_entry.rb
@@ -136,6 +140,8 @@ files:
136
140
  - lib/legion/data/models/task.rb
137
141
  - lib/legion/data/models/task_log.rb
138
142
  - lib/legion/data/settings.rb
143
+ - lib/legion/data/storage_tiers.rb
144
+ - lib/legion/data/vector.rb
139
145
  - lib/legion/data/version.rb
140
146
  - sonar-project.properties
141
147
  homepage: https://github.com/LegionIO/legion-data