legion-data 1.6.25 → 1.6.27

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: 6d2adcdaec18362d8417478957f8093bd331e6e047fd5546b4c9c1151174935e
4
- data.tar.gz: 02aed26a7474699c18956263d1ac639ca688b2e76db37905d230e7eed9af13b7
3
+ metadata.gz: d2fca06857473b0c009e6ab0ca7044f09b394ce3652adad2b35e57835bceb5eb
4
+ data.tar.gz: ea80dac3fee5e54500534c5399c4f1c5b47b9a68ee6c0d0bf0d5ebe344053531
5
5
  SHA512:
6
- metadata.gz: e5343b72716892156ab69870bd50625fdf84b5b54d6792b6838b9783ccf2808c61e1ed2782c6f0a4610569ad771c09856ecc151bb1dd2c3dfdba839f8cc06bab
7
- data.tar.gz: 2e780ebae82893bdbf4a91d0b6d68bce7a1fb3ed2e5dfe0630c6290b67a4ea8bf60f17bdafc0794cbe73fae2dc2a36b5a71fcc0c9955428abf1eb51fc7280538
6
+ metadata.gz: '009fcbd9de6e5dd5d8e74d1ccf2d21c8692a930ae15369ae19fccb2a5f7829982ba6bf79757c66af25d4cce32c5caaf4b8350316e98261676e1bc532485302d4'
7
+ data.tar.gz: d36ede441cdf282214ff9abc7836f2ba07f2eb02bce839f884bf074a7cacea3336ddfdb0182ff87c4e1ba63e54f0bc26c3d9eb1769b607474cc3a32197c814fd
data/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.6.27] - 2026-04-17
6
+
7
+ ### Fixed
8
+ - `load_sequel_model` now reads `settings[:models][:continue_on_load_fail]` (was erroneously reading `continue_on_fail`, a key that never existed — LoadError was always re-raised regardless of the setting). (Fixes #22)
9
+ - `load_models` now skips model loading when `settings[:models][:autoload]` is `false`, honoring the documented knob. (Fixes #22)
10
+ - Audit record live-path specs (`append`, `verify`, `walk`, `query_by_type`, immutability guards) are now self-sufficient: the `before` block reconnects the DB if a prior spec tore it down, eliminating 19 pending examples when running the full suite. (Fixes #22)
11
+ - Default `preconnect` changed from `'concurrently'` to `false`. The concurrent preconnect mode spawned background threads that emitted noisy connection errors when a network adapter was unreachable before dev-fallback could catch the failure. `false` preserves identical behavior for SQLite (default) and avoids the noise for production deployments where operators can opt-in explicitly. (Fixes #22)
12
+
13
+ ## [1.6.26] - 2026-04-17
14
+
15
+ ### Fixed
16
+ - `connection_validation_timeout` default reduced from 600s to -1 (validate every checkout) for non-SQLite adapters. The previous 10-minute window meant stale PG connections from a VPN drop, sleep/wake, or network interface change were not evicted until the next scheduled validation cycle, causing `Sequel::DatabaseDisconnectError` to repeat on every actor tick. With -1, Sequel pings the connection on every pool checkout and discards dead connections immediately. (Fixes #28)
17
+
5
18
  ## [1.6.24] - 2026-04-13
6
19
 
7
20
  ### Added
@@ -158,11 +158,7 @@ module Legion
158
158
  end
159
159
 
160
160
  def json_dump(obj)
161
- if defined?(Legion::JSON)
162
- Legion::JSON.dump(obj)
163
- else
164
- ::JSON.generate(obj)
165
- end
161
+ ::JSON.generate(obj)
166
162
  end
167
163
 
168
164
  def gzip_compress(data)
@@ -25,7 +25,7 @@ module Legion
25
25
  conn = Legion::Data.connection
26
26
  conn.transaction do
27
27
  parent_hash = latest_chain_hash(conn, chain_id)
28
- ts = Time.now
28
+ ts = truncate_to_us(Time.now)
29
29
  ch = compute_chain_hash(parent_hash, content_hash, ts, content_type)
30
30
  sig = sign ? sign_record(ch) : nil
31
31
  meta_json = metadata.empty? ? nil : Legion::JSON.dump(metadata)
@@ -104,31 +104,35 @@ module Legion
104
104
  ds.order(Sequel.desc(:created_at)).limit(limit).all.map { |r| deserialize(r) }
105
105
  end
106
106
 
107
- # SHA-256 of "parent_hash:content_hash:unix_ts_ns:content_type".
107
+ # SHA-256 of "parent_hash:content_hash:unix_ts_us:content_type".
108
108
  #
109
- # The timestamp is normalised to nanoseconds-since-epoch so the hash is
110
- # independent of time zone, string formatting, and database type.
111
- # Exposed as a public method so callers can independently verify a hash
112
- # without querying the database.
109
+ # The timestamp is normalised to microseconds-since-epoch. PostgreSQL
110
+ # TIMESTAMP columns have microsecond precision, so nanosecond values
111
+ # written by Ruby would be truncated on read, causing recomputed hashes
112
+ # to diverge. Microsecond normalisation keeps write-time and read-time
113
+ # hashes identical across all supported adapters.
113
114
  def compute_chain_hash(parent_hash, content_hash, timestamp, content_type)
114
- ts_ns = normalise_timestamp_ns(timestamp)
115
- Digest::SHA256.hexdigest("#{parent_hash}:#{content_hash}:#{ts_ns}:#{content_type}")
115
+ ts_us = normalise_timestamp_us(timestamp)
116
+ Digest::SHA256.hexdigest("#{parent_hash}:#{content_hash}:#{ts_us}:#{content_type}")
116
117
  end
117
118
 
118
119
  private
119
120
 
120
- # Normalise a timestamp to integer nanoseconds-since-epoch regardless of
121
- # whether the database returned a Time, DateTime, or String.
122
- def normalise_timestamp_ns(timestamp)
123
- case timestamp
124
- when ::Time
125
- (timestamp.to_r * 1_000_000_000).to_i
126
- when ::DateTime
127
- (timestamp.to_time.to_r * 1_000_000_000).to_i
128
- else
129
- ts = ::Time.parse(timestamp.to_s)
130
- (ts.to_r * 1_000_000_000).to_i
131
- end
121
+ # Normalise a timestamp to integer microseconds-since-epoch regardless of
122
+ # whether the database returned a Time, DateTime, or String. Always uses
123
+ # the absolute epoch value so timezone differences don't affect the hash.
124
+ def normalise_timestamp_us(timestamp)
125
+ t = case timestamp
126
+ when ::Time then timestamp
127
+ when ::DateTime then timestamp.to_time
128
+ else ::Time.parse(timestamp.to_s)
129
+ end
130
+ (t.to_r * 1_000_000).to_i
131
+ end
132
+
133
+ def truncate_to_us(time)
134
+ us = (time.to_r * 1_000_000).to_i
135
+ ::Time.at(Rational(us, 1_000_000))
132
136
  end
133
137
 
134
138
  def latest_chain_hash(conn, chain_id)
@@ -37,7 +37,7 @@ module Legion
37
37
  model
38
38
  rescue LoadError => e
39
39
  handle_exception(e, level: :fatal, operation: :load_sequel_model, model: model)
40
- raise e unless Legion::Settings[:data][:models][:continue_on_fail]
40
+ raise e unless Legion::Settings[:data][:models][:continue_on_load_fail]
41
41
  end
42
42
  end
43
43
  end
@@ -35,7 +35,7 @@ module Legion
35
35
  # Connection pool
36
36
  max_connections: 25,
37
37
  pool_timeout: 5,
38
- preconnect: 'concurrently',
38
+ preconnect: false,
39
39
  single_threaded: false,
40
40
  test: true,
41
41
  name: nil,
@@ -48,8 +48,9 @@ module Legion
48
48
  sql_log_level: 'debug',
49
49
 
50
50
  # Connection health (network adapters only, ignored for sqlite)
51
+ # -1 means validate on every checkout, catching stale connections from VPN/sleep/network changes immediately
51
52
  connection_validation: true,
52
- connection_validation_timeout: 600,
53
+ connection_validation_timeout: -1,
53
54
  connection_expiration: true,
54
55
  connection_expiration_timeout: 14_400,
55
56
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Data
5
- VERSION = '1.6.25'
5
+ VERSION = '1.6.27'
6
6
  end
7
7
  end
data/lib/legion/data.rb CHANGED
@@ -64,6 +64,8 @@ module Legion
64
64
  end
65
65
 
66
66
  def load_models
67
+ return unless Legion::Settings[:data][:models][:autoload] != false
68
+
67
69
  Legion::Data::Models.load
68
70
  end
69
71
 
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.6.25
4
+ version: 1.6.27
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity