legion-data 1.4.12 → 1.4.13
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/CHANGELOG.md +24 -0
- data/CODEOWNERS +40 -0
- data/lib/legion/data/archival.rb +2 -0
- data/lib/legion/data/archiver.rb +5 -0
- data/lib/legion/data/connection.rb +10 -0
- data/lib/legion/data/encryption/key_provider.rb +2 -0
- data/lib/legion/data/encryption/sequel_plugin.rb +6 -1
- data/lib/legion/data/event_store.rb +9 -2
- data/lib/legion/data/retention.rb +2 -0
- data/lib/legion/data/spool.rb +2 -0
- data/lib/legion/data/storage_tiers.rb +2 -0
- data/lib/legion/data/vector.rb +3 -0
- data/lib/legion/data/version.rb +1 -1
- data/lib/legion/data.rb +2 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c43d3a8453436e0a8b9293c6cd8f23686f1e73550ed62d27958ecc6a4e55ba02
|
|
4
|
+
data.tar.gz: 48da4423bb162af7d1e95ba854bce106131b8b1d6cc2e98f6d9205bfd32eb5e1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 96884671abf0ef7ed9fdef58c85e332a186043d35d9bd29210d158fb92a3f7bc7d2e60e8ec5a759f7d854fe89ef7892773b4c81cb841ad8655a103da3401fed4
|
|
7
|
+
data.tar.gz: 875a80126a77632a4af0604bbb447ddf54646ae37e072f3018a789270b8fb258cf214fc02aa3a639c6c31bcba504c7e639ee4141d5906524eaa5561c9b418a2a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Legion::Data Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.13] - 2026-03-22
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Comprehensive logging across data operations: connection lifecycle, archival, retention, storage tiers, event store, encryption key provider, spool drain, and vector search
|
|
7
|
+
- `Connection.setup`: `.info` on successful connect (adapter://host:port/db or SQLite path)
|
|
8
|
+
- `Connection.shutdown`: `.info` on disconnect
|
|
9
|
+
- `Connection.connect_with_replicas`: `.debug` with replica count
|
|
10
|
+
- `Data.setup`: `.info` on setup completion
|
|
11
|
+
- `Data.shutdown`: `.info` on shutdown
|
|
12
|
+
- `Archiver.archive_table`: `.info` on start and completion with table name and row count; `.warn` before re-raising S3/Azure upload failures
|
|
13
|
+
- `Archival.archive!`: `.info` with table, destination, cutoff, and dry_run flag; `.info` on restore with row count
|
|
14
|
+
- `Retention.archive_old_records`: `.info` with table name and archived row count
|
|
15
|
+
- `Retention.purge_expired_records`: `.info` with archive table name and purged row count
|
|
16
|
+
- `StorageTiers.archive_to_warm`: `.info` with table name and row count
|
|
17
|
+
- `StorageTiers.export_to_cold`: `.info` with exported row count
|
|
18
|
+
- `EventStore.append`: `.debug` with stream, event type, and sequence number
|
|
19
|
+
- `EventStore.verify_chain`: `.warn` when hash chain is broken, with stream and sequence number
|
|
20
|
+
- `Encryption::KeyProvider`: `.warn` on dev key fallback; `.debug` on Vault key derivation
|
|
21
|
+
- `Encryption::SequelPlugin`: `.warn` on decrypt failure before re-raise
|
|
22
|
+
- `Spool#write`: `.debug` with sub-namespace and filename
|
|
23
|
+
- `Spool#flush`: `.info` with sub-namespace and drained item count
|
|
24
|
+
- `Vector.ensure_extension!`: `.info` on successful pgvector setup
|
|
25
|
+
- `Vector.cosine_search` / `Vector.l2_search`: `.debug` with table, column, and limit
|
|
26
|
+
|
|
3
27
|
## [1.4.12] - 2026-03-21
|
|
4
28
|
|
|
5
29
|
### Added
|
data/CODEOWNERS
CHANGED
|
@@ -1 +1,41 @@
|
|
|
1
|
+
# Default owner — all files
|
|
1
2
|
* @Esity
|
|
3
|
+
|
|
4
|
+
# Core library code
|
|
5
|
+
# lib/ @Esity @future-core-team
|
|
6
|
+
|
|
7
|
+
# Database connection
|
|
8
|
+
# lib/legion/data/connection.rb @Esity @future-infra-team
|
|
9
|
+
|
|
10
|
+
# Migrations
|
|
11
|
+
# lib/legion/data/migrations/ @Esity @future-core-team
|
|
12
|
+
|
|
13
|
+
# Models
|
|
14
|
+
# lib/legion/data/models/ @Esity @future-core-team
|
|
15
|
+
|
|
16
|
+
# Local SQLite (agentic cognitive state)
|
|
17
|
+
# lib/legion/data/local.rb @Esity @future-ai-team
|
|
18
|
+
# lib/legion/data/local/ @Esity @future-ai-team
|
|
19
|
+
|
|
20
|
+
# Encryption at rest
|
|
21
|
+
# lib/legion/data/encryption/ @Esity @future-security-team
|
|
22
|
+
|
|
23
|
+
# Event store (governance)
|
|
24
|
+
# lib/legion/data/event_store/ @Esity @future-security-team
|
|
25
|
+
# lib/legion/data/event_store.rb @Esity @future-security-team
|
|
26
|
+
|
|
27
|
+
# Vector helpers (pgvector / Apollo)
|
|
28
|
+
# lib/legion/data/vector.rb @Esity @future-ai-team
|
|
29
|
+
|
|
30
|
+
# Storage tiers and archival
|
|
31
|
+
# lib/legion/data/storage_tiers.rb @Esity @future-infra-team
|
|
32
|
+
# lib/legion/data/archival/ @Esity @future-infra-team
|
|
33
|
+
|
|
34
|
+
# Specs
|
|
35
|
+
# spec/ @Esity @future-contributors
|
|
36
|
+
|
|
37
|
+
# Documentation
|
|
38
|
+
# *.md @Esity @future-docs-team
|
|
39
|
+
|
|
40
|
+
# CI/CD
|
|
41
|
+
# .github/ @Esity
|
data/lib/legion/data/archival.rb
CHANGED
|
@@ -18,6 +18,7 @@ module Legion
|
|
|
18
18
|
archive_table = ARCHIVE_TABLE_MAP[table]
|
|
19
19
|
next unless archive_table && db_ready?(table) && db_ready?(archive_table)
|
|
20
20
|
|
|
21
|
+
Legion::Logging.info "Archiving #{table} -> #{archive_table} (cutoff: #{policy.warm_cutoff}, dry_run: #{dry_run})" if defined?(Legion::Logging)
|
|
21
22
|
count = archive_table!(
|
|
22
23
|
source: table, destination: archive_table,
|
|
23
24
|
cutoff: policy.warm_cutoff, batch_size: policy.batch_size, dry_run: dry_run
|
|
@@ -45,6 +46,7 @@ module Legion
|
|
|
45
46
|
end
|
|
46
47
|
conn[archive_table].where(original_id: ids).delete
|
|
47
48
|
end
|
|
49
|
+
Legion::Logging.info "Restored #{restored} row(s) from #{archive_table} -> #{source_table}" if defined?(Legion::Logging)
|
|
48
50
|
restored
|
|
49
51
|
end
|
|
50
52
|
|
data/lib/legion/data/archiver.rb
CHANGED
|
@@ -16,6 +16,8 @@ module Legion
|
|
|
16
16
|
def archive_table(table:, retention_days: 90, batch_size: 1000, storage_backend: nil)
|
|
17
17
|
return { skipped: true, reason: 'not_postgres' } unless postgres?
|
|
18
18
|
|
|
19
|
+
Legion::Logging.info "Archiving table #{table} (retention: #{retention_days}d)" if defined?(Legion::Logging)
|
|
20
|
+
|
|
19
21
|
conn = Legion::Data.connection
|
|
20
22
|
cutoff = Time.now - (retention_days * 86_400)
|
|
21
23
|
now = Time.now.utc
|
|
@@ -62,6 +64,7 @@ module Legion
|
|
|
62
64
|
paths << path
|
|
63
65
|
end
|
|
64
66
|
|
|
67
|
+
Legion::Logging.info "Archived #{total_rows} rows from #{table} in #{batches} batch(es)" if defined?(Legion::Logging)
|
|
65
68
|
{ batches: batches, total_rows: total_rows, paths: paths }
|
|
66
69
|
end
|
|
67
70
|
|
|
@@ -134,6 +137,7 @@ module Legion
|
|
|
134
137
|
rescue UploadError
|
|
135
138
|
raise
|
|
136
139
|
rescue StandardError => e
|
|
140
|
+
Legion::Logging.warn "S3 upload failed: #{e.message}" if defined?(Legion::Logging)
|
|
137
141
|
raise UploadError, "S3 upload failed: #{e.message}"
|
|
138
142
|
end
|
|
139
143
|
|
|
@@ -148,6 +152,7 @@ module Legion
|
|
|
148
152
|
rescue UploadError
|
|
149
153
|
raise
|
|
150
154
|
rescue StandardError => e
|
|
155
|
+
Legion::Logging.warn "Azure upload failed: #{e.message}" if defined?(Legion::Logging)
|
|
151
156
|
raise UploadError, "Azure upload failed: #{e.message}"
|
|
152
157
|
end
|
|
153
158
|
|
|
@@ -33,6 +33,14 @@ module Legion
|
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
Legion::Settings[:data][:connected] = true
|
|
36
|
+
if defined?(Legion::Logging)
|
|
37
|
+
if adapter == :sqlite
|
|
38
|
+
Legion::Logging.info "Connected to SQLite at #{sqlite_path}"
|
|
39
|
+
else
|
|
40
|
+
creds = Legion::Data::Settings.creds(adapter)
|
|
41
|
+
Legion::Logging.info "Connected to #{adapter}://#{creds[:host]}:#{creds[:port]}/#{creds[:database] || creds[:db]}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
36
44
|
configure_logging
|
|
37
45
|
connect_with_replicas
|
|
38
46
|
end
|
|
@@ -40,6 +48,7 @@ module Legion
|
|
|
40
48
|
def shutdown
|
|
41
49
|
@sequel&.disconnect
|
|
42
50
|
Legion::Settings[:data][:connected] = false
|
|
51
|
+
Legion::Logging.info 'Legion::Data connection closed' if defined?(Legion::Logging)
|
|
43
52
|
end
|
|
44
53
|
|
|
45
54
|
def connect_with_replicas
|
|
@@ -61,6 +70,7 @@ module Legion
|
|
|
61
70
|
end
|
|
62
71
|
|
|
63
72
|
@replica_servers = replica_list.each_with_index.map { |_, idx| :"read_#{idx}" }
|
|
73
|
+
Legion::Logging.debug "Registered #{@replica_servers.size} read replica(s)" if defined?(Legion::Logging)
|
|
64
74
|
end
|
|
65
75
|
|
|
66
76
|
def read_server
|
|
@@ -24,10 +24,12 @@ module Legion
|
|
|
24
24
|
|
|
25
25
|
def derive_key(tenant_id)
|
|
26
26
|
if tenant_id && crypt_available?
|
|
27
|
+
Legion::Logging.debug "Deriving Vault key for tenant #{tenant_id}" if defined?(Legion::Logging)
|
|
27
28
|
Legion::Crypt::PartitionKeys.derive(tenant_id: tenant_id)
|
|
28
29
|
elsif crypt_available?
|
|
29
30
|
Legion::Crypt.default_encryption_key
|
|
30
31
|
else
|
|
32
|
+
Legion::Logging.warn 'Legion::Crypt unavailable, falling back to dev encryption key' if defined?(Legion::Logging)
|
|
31
33
|
local_key
|
|
32
34
|
end
|
|
33
35
|
end
|
|
@@ -24,7 +24,12 @@ module Legion
|
|
|
24
24
|
tenant = col_scope == :tenant ? self[:tenant_id] : nil
|
|
25
25
|
key = provider.key_for(tenant_id: tenant)
|
|
26
26
|
aad = "#{self.class.table_name}:#{pk}:#{name}"
|
|
27
|
-
|
|
27
|
+
begin
|
|
28
|
+
Legion::Data::Encryption::Cipher.decrypt(raw.b, key: key, aad: aad)
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
Legion::Logging.warn "Decrypt failed for #{self.class.table_name}##{pk} column #{name}: #{e.message}" if defined?(Legion::Logging)
|
|
31
|
+
raise
|
|
32
|
+
end
|
|
28
33
|
end
|
|
29
34
|
|
|
30
35
|
define_method(:"#{name}=") do |value|
|
|
@@ -42,6 +42,7 @@ module Legion
|
|
|
42
42
|
created_at: Time.now
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
+
Legion::Logging.debug "EventStore append: stream=#{stream} type=#{type} seq=#{seq}" if defined?(Legion::Logging)
|
|
45
46
|
{ stream: stream, sequence: seq, hash: event_hash }
|
|
46
47
|
end
|
|
47
48
|
end
|
|
@@ -73,8 +74,14 @@ module Legion
|
|
|
73
74
|
prev_hash = '0' * 64
|
|
74
75
|
events.each do |e|
|
|
75
76
|
expected = compute_hash(stream, e[:sequence_number], e[:event_type], e[:data_json], prev_hash)
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
unless e[:event_hash] == expected
|
|
78
|
+
Legion::Logging.warn "EventStore chain broken: stream=#{stream} seq=#{e[:sequence_number]}" if defined?(Legion::Logging)
|
|
79
|
+
return { valid: false, broken_at: e[:sequence_number] }
|
|
80
|
+
end
|
|
81
|
+
unless e[:previous_hash] == prev_hash
|
|
82
|
+
Legion::Logging.warn "EventStore chain broken: stream=#{stream} seq=#{e[:sequence_number]}" if defined?(Legion::Logging)
|
|
83
|
+
return { valid: false, broken_at: e[:sequence_number] }
|
|
84
|
+
end
|
|
78
85
|
|
|
79
86
|
prev_hash = e[:event_hash]
|
|
80
87
|
end
|
|
@@ -26,6 +26,7 @@ module Legion
|
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
Legion::Logging.info "Archived #{count} row(s) from #{table}" if defined?(Legion::Logging) && count.positive?
|
|
29
30
|
{ archived: count, table: table }
|
|
30
31
|
end
|
|
31
32
|
|
|
@@ -38,6 +39,7 @@ module Legion
|
|
|
38
39
|
expired = db[archive_table].where(Sequel.lit("#{date_column} < ?", cutoff))
|
|
39
40
|
count = expired.count
|
|
40
41
|
expired.delete if count.positive?
|
|
42
|
+
Legion::Logging.info "Purged #{count} expired row(s) from #{archive_table}" if defined?(Legion::Logging) && count.positive?
|
|
41
43
|
|
|
42
44
|
{ purged: count, table: table }
|
|
43
45
|
end
|
data/lib/legion/data/spool.rb
CHANGED
|
@@ -41,6 +41,7 @@ module Legion
|
|
|
41
41
|
filename = "#{Time.now.strftime('%s%9N')}-#{SecureRandom.uuid}.json"
|
|
42
42
|
path = File.join(dir, filename)
|
|
43
43
|
File.write(path, ::JSON.generate(payload))
|
|
44
|
+
Legion::Logging.debug "Spool write: #{sub_namespace} -> #{filename}" if defined?(Legion::Logging)
|
|
44
45
|
path
|
|
45
46
|
end
|
|
46
47
|
|
|
@@ -56,6 +57,7 @@ module Legion
|
|
|
56
57
|
File.delete(path)
|
|
57
58
|
count += 1
|
|
58
59
|
end
|
|
60
|
+
Legion::Logging.info "Spool drained #{count} item(s) from #{sub_namespace}" if defined?(Legion::Logging) && count.positive?
|
|
59
61
|
count
|
|
60
62
|
end
|
|
61
63
|
|
|
@@ -28,6 +28,7 @@ module Legion
|
|
|
28
28
|
Legion::Data.connection[table].where(id: ids).delete
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
Legion::Logging.info "Archived #{records.size} row(s) from #{table} to warm tier" if defined?(Legion::Logging)
|
|
31
32
|
{ archived: records.size, table: table.to_s }
|
|
32
33
|
end
|
|
33
34
|
|
|
@@ -43,6 +44,7 @@ module Legion
|
|
|
43
44
|
|
|
44
45
|
ids = records.map { |r| r[:id] }
|
|
45
46
|
Legion::Data.connection[:data_archive].where(id: ids).update(tier: TIERS[:cold])
|
|
47
|
+
Legion::Logging.info "Exported #{records.size} row(s) to cold tier" if defined?(Legion::Logging)
|
|
46
48
|
{ exported: records.size, data: records }
|
|
47
49
|
end
|
|
48
50
|
|
data/lib/legion/data/vector.rb
CHANGED
|
@@ -17,6 +17,7 @@ module Legion
|
|
|
17
17
|
return false unless Legion::Data.connection&.adapter_scheme == :postgres
|
|
18
18
|
|
|
19
19
|
Legion::Data.connection.run('CREATE EXTENSION IF NOT EXISTS vector')
|
|
20
|
+
Legion::Logging.info 'pgvector extension enabled' if defined?(Legion::Logging)
|
|
20
21
|
true
|
|
21
22
|
rescue StandardError => e
|
|
22
23
|
Legion::Logging.warn("pgvector extension creation failed: #{e.message}") if defined?(Legion::Logging)
|
|
@@ -26,6 +27,7 @@ module Legion
|
|
|
26
27
|
def cosine_search(table:, column:, query_vector:, limit: 10, min_similarity: 0.0)
|
|
27
28
|
return [] unless available?
|
|
28
29
|
|
|
30
|
+
Legion::Logging.debug "Vector cosine_search: table=#{table} column=#{column} limit=#{limit}" if defined?(Legion::Logging)
|
|
29
31
|
vec_literal = vector_literal(query_vector)
|
|
30
32
|
ds = Legion::Data.connection[table]
|
|
31
33
|
.select_all
|
|
@@ -40,6 +42,7 @@ module Legion
|
|
|
40
42
|
def l2_search(table:, column:, query_vector:, limit: 10)
|
|
41
43
|
return [] unless available?
|
|
42
44
|
|
|
45
|
+
Legion::Logging.debug "Vector l2_search: table=#{table} column=#{column} limit=#{limit}" if defined?(Legion::Logging)
|
|
43
46
|
vec_literal = vector_literal(query_vector)
|
|
44
47
|
Legion::Data.connection[table]
|
|
45
48
|
.select_all
|
data/lib/legion/data/version.rb
CHANGED
data/lib/legion/data.rb
CHANGED
|
@@ -21,6 +21,7 @@ module Legion
|
|
|
21
21
|
load_models
|
|
22
22
|
setup_cache
|
|
23
23
|
setup_local
|
|
24
|
+
Legion::Logging.info 'Legion::Data setup complete' if defined?(Legion::Logging)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def connection_setup
|
|
@@ -66,6 +67,7 @@ module Legion
|
|
|
66
67
|
def shutdown
|
|
67
68
|
Legion::Data::Local.shutdown if defined?(Legion::Data::Local) && Legion::Data::Local.connected?
|
|
68
69
|
Legion::Data::Connection.shutdown
|
|
70
|
+
Legion::Logging.info 'Legion::Data shutdown complete' if defined?(Legion::Logging)
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
private
|