legion-data 1.4.12 → 1.4.14

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: 32985bb416df4b3bf1d135609260830412479d6176d4503aa82e1ff4934382a3
4
- data.tar.gz: eb1d09e394c743f3334eeb0f3aee9584889a7a4863f190062f6b127dba246ad5
3
+ metadata.gz: 847590baf84ea8855aa32b779819bf96d99aa94308214aee82d5b742d04b4381
4
+ data.tar.gz: a3d8f45c65a097fcbba5c45fb46ada782fdbc276637076eecafe29d09ed236c9
5
5
  SHA512:
6
- metadata.gz: 3c23bc39ed0649ed989e484947a33ca85f93b7a5f2b7ea487e862c8aa944c15b60f21c83a162a9ce45ea93b0f5221b66a2ca98f43e016ea1f2b7031ef9e3eee3
7
- data.tar.gz: b68e6b6f32262123983356bdfb2a26d243c2e96fbabc3057c285b65eb59f78578e0137147ad82c18f8af8d000f7a25fde9618c54fb9c060e42286a71f8465dea
6
+ metadata.gz: a30c4d692bae6ff2f7d816b740ba6d05b2d4ca7088b48f23d8706e15968a6a6614cf5baa9dedcdee442a87fed0870709daa63270da755077871ea805bffe24ba
7
+ data.tar.gz: d59ba3244ca4e394cf5d9f3a236597ba95f6a96f846472f95d4e9da45454b873c833039070e397fc43b6b23ef7056f8e391503ec28a1bfc595fa0056cf1be87b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Legion::Data Changelog
2
2
 
3
+ ## [1.4.14] - 2026-03-22
4
+
5
+ ### Changed
6
+ - Boot connection log for non-SQLite adapters now includes username: `adapter://user@host:port/db`
7
+
8
+ ## [1.4.13] - 2026-03-22
9
+
10
+ ### Added
11
+ - Comprehensive logging across data operations: connection lifecycle, archival, retention, storage tiers, event store, encryption key provider, spool drain, and vector search
12
+ - `Connection.setup`: `.info` on successful connect (adapter://host:port/db or SQLite path)
13
+ - `Connection.shutdown`: `.info` on disconnect
14
+ - `Connection.connect_with_replicas`: `.debug` with replica count
15
+ - `Data.setup`: `.info` on setup completion
16
+ - `Data.shutdown`: `.info` on shutdown
17
+ - `Archiver.archive_table`: `.info` on start and completion with table name and row count; `.warn` before re-raising S3/Azure upload failures
18
+ - `Archival.archive!`: `.info` with table, destination, cutoff, and dry_run flag; `.info` on restore with row count
19
+ - `Retention.archive_old_records`: `.info` with table name and archived row count
20
+ - `Retention.purge_expired_records`: `.info` with archive table name and purged row count
21
+ - `StorageTiers.archive_to_warm`: `.info` with table name and row count
22
+ - `StorageTiers.export_to_cold`: `.info` with exported row count
23
+ - `EventStore.append`: `.debug` with stream, event type, and sequence number
24
+ - `EventStore.verify_chain`: `.warn` when hash chain is broken, with stream and sequence number
25
+ - `Encryption::KeyProvider`: `.warn` on dev key fallback; `.debug` on Vault key derivation
26
+ - `Encryption::SequelPlugin`: `.warn` on decrypt failure before re-raise
27
+ - `Spool#write`: `.debug` with sub-namespace and filename
28
+ - `Spool#flush`: `.info` with sub-namespace and drained item count
29
+ - `Vector.ensure_extension!`: `.info` on successful pgvector setup
30
+ - `Vector.cosine_search` / `Vector.l2_search`: `.debug` with table, column, and limit
31
+
3
32
  ## [1.4.12] - 2026-03-21
4
33
 
5
34
  ### 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
@@ -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
 
@@ -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,18 @@ 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
+ user = creds[:user] || creds[:username] || 'unknown'
42
+ host = creds[:host] || '127.0.0.1'
43
+ port = creds[:port]
44
+ db = creds[:database] || creds[:db]
45
+ Legion::Logging.info "Connected to #{adapter}://#{user}@#{host}:#{port}/#{db}"
46
+ end
47
+ end
36
48
  configure_logging
37
49
  connect_with_replicas
38
50
  end
@@ -40,6 +52,7 @@ module Legion
40
52
  def shutdown
41
53
  @sequel&.disconnect
42
54
  Legion::Settings[:data][:connected] = false
55
+ Legion::Logging.info 'Legion::Data connection closed' if defined?(Legion::Logging)
43
56
  end
44
57
 
45
58
  def connect_with_replicas
@@ -61,6 +74,7 @@ module Legion
61
74
  end
62
75
 
63
76
  @replica_servers = replica_list.each_with_index.map { |_, idx| :"read_#{idx}" }
77
+ Legion::Logging.debug "Registered #{@replica_servers.size} read replica(s)" if defined?(Legion::Logging)
64
78
  end
65
79
 
66
80
  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
- Legion::Data::Encryption::Cipher.decrypt(raw.b, key: key, aad: aad)
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
- return { valid: false, broken_at: e[:sequence_number] } unless e[:event_hash] == expected
77
- return { valid: false, broken_at: e[:sequence_number] } unless e[:previous_hash] == prev_hash
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
@@ -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
 
@@ -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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Data
5
- VERSION = '1.4.12'
5
+ VERSION = '1.4.14'
6
6
  end
7
7
  end
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
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.12
4
+ version: 1.4.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity