familia 2.0.0.pre26 → 2.0.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.
@@ -1,306 +0,0 @@
1
- # Migrating Guide: v2.0.0-pre12
2
-
3
- This version introduces significant security improvements to Familia's identifier system, including verifiable identifiers with HMAC signatures, scoped identifier namespaces, and hardened external identifier derivation to prevent potential security vulnerabilities.
4
-
5
- ## VerifiableIdentifier Feature
6
-
7
- ### Overview
8
-
9
- The new `Familia::VerifiableIdentifier` module allows applications to create and verify identifiers with embedded HMAC signatures. This enables stateless confirmation that an identifier was generated by your application, preventing forged IDs from malicious sources.
10
-
11
- ### Basic Usage
12
-
13
- ```ruby
14
- class Customer < Familia::Horreum
15
- feature :verifiable_identifier
16
-
17
- # Required: UnsortedSet the HMAC secret (do this once in your app initialization)
18
- # Generate with: SecureRandom.hex(64)
19
- ENV['VERIFIABLE_ID_HMAC_SECRET'] = 'your_64_character_hex_secret'
20
- end
21
-
22
- # Generate a verifiable identifier
23
- customer = Customer.new
24
- verifiable_id = customer.generate_verifiable_id
25
- # => "cust_1234567890abcdef_a1b2c3d4e5f6789..."
26
-
27
- # Verify the identifier later (stateless verification)
28
- if Customer.verified_identifier?(verifiable_id)
29
- # Identifier is valid and was generated by this application
30
- original_id = Customer.extract_identifier(verifiable_id)
31
- customer = Customer.new(original_id)
32
- else
33
- # Identifier is forged or corrupted
34
- raise SecurityError, "Invalid identifier"
35
- end
36
- ```
37
-
38
- ### Scoped VerifiableIdentifier
39
-
40
- The new `scope` parameter enables cryptographically isolated identifier namespaces for multi-tenant, multi-domain, or multi-environment applications.
41
-
42
- #### Before (Global Scope)
43
- ```ruby
44
- # All identifiers share the same cryptographic space
45
- admin_id = admin.generate_verifiable_id
46
- user_id = user.generate_verifiable_id
47
-
48
- # Risk: Cross-contamination between different contexts
49
- ```
50
-
51
- #### After (Scoped Namespaces)
52
- ```ruby
53
- # Production environment
54
- prod_customer_id = customer.generate_verifiable_id(scope: 'production')
55
- prod_admin_id = admin.generate_verifiable_id(scope: 'production:admin')
56
-
57
- # Development environment
58
- dev_customer_id = customer.generate_verifiable_id(scope: 'development')
59
-
60
- # Multi-tenant application
61
- tenant_a_id = user.generate_verifiable_id(scope: "tenant:#{tenant_a.id}")
62
- tenant_b_id = user.generate_verifiable_id(scope: "tenant:#{tenant_b.id}")
63
-
64
- # Verification requires matching scope
65
- Customer.verified_identifier?(prod_customer_id, scope: 'production') # => true
66
- Customer.verified_identifier?(prod_customer_id, scope: 'development') # => false
67
- ```
68
-
69
- **Scope Benefits:**
70
- - **Multi-tenant isolation**: Tenant A cannot forge identifiers for Tenant B
71
- - **Environment separation**: Production IDs cannot be used in development
72
- - **Role-based security**: Admin scopes separate from user scopes
73
- - **Full backward compatibility**: Existing code without scopes continues to work
74
-
75
- ### Key Management
76
-
77
- #### Secure Secret Generation
78
- ```ruby
79
- # Generate a cryptographically secure HMAC secret
80
- require 'securerandom'
81
- secret = SecureRandom.hex(64) # 512-bit secret
82
- puts "VERIFIABLE_ID_HMAC_SECRET=#{secret}"
83
- ```
84
-
85
- #### Environment Configuration
86
- ```ruby
87
- # config/application.rb or equivalent
88
- # UnsortedSet this BEFORE any VerifiableIdentifier usage
89
- ENV['VERIFIABLE_ID_HMAC_SECRET'] = Rails.application.credentials.verifiable_id_secret
90
-
91
- # Or configure programmatically
92
- Familia::VerifiableIdentifier.hmac_secret = your_secret_string
93
- ```
94
-
95
- ## ObjectIdentifier Feature Improvements
96
-
97
- ### Method Renaming
98
-
99
- Method names have been updated for clarity and consistency:
100
-
101
- #### Before
102
- ```ruby
103
- customer = Customer.new
104
- objid = customer.generate_objid # Unclear what this generates
105
- extid = Customer.generate_extid(objid) # Less secure class method
106
- ```
107
-
108
- #### After
109
- ```ruby
110
- customer = Customer.new
111
- objid = customer.generate_object_identifier # Clear: generates object ID
112
- extid = customer.derive_external_identifier # Clear: derives from objid, instance method
113
- ```
114
-
115
- **Migration:**
116
- - Replace `generate_objid` → `generate_object_identifier`
117
- - Replace `generate_external_identifier` → `derive_external_identifier`
118
- - Remove usage of `generate_extid` (deprecated for security reasons)
119
-
120
- ### Provenance Tracking
121
-
122
- ObjectIdentifier now tracks which generator was used for each identifier:
123
-
124
- ```ruby
125
- class Customer < Familia::Horreum
126
- feature :object_identifier
127
-
128
- # Configure generator type
129
- object_identifier_generator :uuid_v7 # or :uuid_v4, :hex, custom proc
130
- end
131
-
132
- customer = Customer.new
133
- objid = customer.generate_object_identifier
134
-
135
- # Provenance information available
136
- puts customer.object_identifier_generator_type # => :uuid_v7
137
- puts customer.objid_format # => :uuid (normalized format)
138
- ```
139
-
140
- **Benefits:**
141
- - **Security auditing**: Know which generator created each identifier
142
- - **Format normalization**: Eliminates ambiguity between UUID and hex formats
143
- - **Migration support**: Track mixed generator usage during transitions
144
-
145
- ## ExternalIdentifier Security Hardening
146
-
147
- ### Provenance Validation
148
-
149
- ExternalIdentifier now validates that objid values come from the ObjectIdentifier feature before deriving external identifiers.
150
-
151
- #### Before (Potential Security Risk)
152
- ```ruby
153
- # Could derive external IDs from any string, including malicious input
154
- extid = customer.derive_external_identifier("malicious_input")
155
- ```
156
-
157
- #### After (Hardened)
158
- ```ruby
159
- customer = Customer.new
160
- customer.generate_object_identifier # Must generate objid first
161
-
162
- # Only works with validated objid from ObjectIdentifier feature
163
- extid = customer.derive_external_identifier # Secure: uses validated objid
164
- ```
165
-
166
- ### Improved Security Model
167
-
168
- External identifiers are now derived using the internal objid as a seed for a new random value, rather than directly deriving from objid.
169
-
170
- #### Before
171
- ```ruby
172
- # Direct derivation could leak information about objid
173
- extid = hash(objid) # Information leakage risk
174
- ```
175
-
176
- #### After
177
- ```ruby
178
- # objid used as seed for new random value
179
- extid = secure_hash(objid + additional_entropy) # No information leakage
180
- ```
181
-
182
- ### Error Handling Improvements
183
-
184
- External identifier now raises clear errors for invalid usage:
185
-
186
- ```ruby
187
- class Customer < Familia::Horreum
188
- feature :external_identifier # Missing: object_identifier dependency
189
- end
190
-
191
- customer = Customer.new
192
- # Raises ExternalIdentifierError instead of returning nil
193
- customer.derive_external_identifier
194
- # => Familia::ExternalIdentifierError: Model does not have an objid field
195
- ```
196
-
197
- ## Migration Steps
198
-
199
- ### 1. Update Method Names
200
-
201
- Replace deprecated method names in your codebase:
202
-
203
- ```bash
204
- # Search and replace patterns:
205
- grep -r "generate_objid" --include="*.rb" .
206
- # Replace with: generate_object_identifier
207
-
208
- grep -r "generate_external_identifier" --include="*.rb" .
209
- # Replace with: derive_external_identifier
210
-
211
- grep -r "generate_extid" --include="*.rb" .
212
- # Remove usage - use derive_external_identifier instead
213
- ```
214
-
215
- ### 2. Add HMAC Secret for VerifiableIdentifier
216
-
217
- If you plan to use VerifiableIdentifier:
218
-
219
- ```ruby
220
- # Generate secret
221
- require 'securerandom'
222
- secret = SecureRandom.hex(64)
223
-
224
- # Add to your environment configuration
225
- # .env, Rails credentials, or similar
226
- VERIFIABLE_ID_HMAC_SECRET=your_generated_secret
227
-
228
- # Verify configuration
229
- puts ENV['VERIFIABLE_ID_HMAC_SECRET']&.length # Should be 128 characters
230
- ```
231
-
232
- ### 3. Update ExternalIdentifier Usage
233
-
234
- Ensure proper dependency chain:
235
-
236
- ```ruby
237
- class YourModel < Familia::Horreum
238
- # Required: ObjectIdentifier must come before ExternalIdentifier
239
- feature :object_identifier
240
- feature :external_identifier
241
-
242
- # Configure generator if needed
243
- object_identifier_generator :uuid_v7
244
- end
245
-
246
- # Usage pattern
247
- model = YourModel.new
248
- model.generate_object_identifier # Generate objid first
249
- extid = model.derive_external_identifier # Then derive external ID
250
- ```
251
-
252
- ### 4. Review Security-Sensitive Code
253
-
254
- Audit any code that processes identifiers from external sources:
255
-
256
- ```ruby
257
- # Before: Potentially unsafe
258
- def process_identifier(external_id)
259
- # Could process forged identifiers
260
- model = Model.find_by_external_id(external_id)
261
- end
262
-
263
- # After: With verification
264
- def process_identifier(verifiable_id)
265
- # Verify identifier authenticity first
266
- unless Model.verified_identifier?(verifiable_id)
267
- raise SecurityError, "Invalid identifier"
268
- end
269
-
270
- original_id = Model.extract_identifier(verifiable_id)
271
- model = Model.new(original_id)
272
- end
273
- ```
274
-
275
- ## Breaking Changes
276
-
277
- 1. **`generate_extid` removed** - Use instance-level `derive_external_identifier` instead
278
- 2. **ExternalIdentifier validation** - Now raises `ExternalIdentifierError` instead of returning `nil` for models without objid
279
- 3. **Method names changed** - `generate_objid` → `generate_object_identifier`, `generate_external_identifier` → `derive_external_identifier`
280
-
281
- ## New Security Capabilities
282
-
283
- 1. **Cryptographic identifier verification** - Prevent forged IDs with HMAC signatures
284
- 2. **Scoped namespaces** - Isolate identifiers by tenant, environment, or role
285
- 3. **Provenance tracking** - Know which generator created each identifier
286
- 4. **Information leakage prevention** - External IDs no longer directly expose internal IDs
287
- 5. **Input validation** - Clear error messages for invalid operations
288
-
289
- ## Testing Your Migration
290
-
291
- ```ruby
292
- # Test ObjectIdentifier changes
293
- model = YourModel.new
294
- objid = model.generate_object_identifier
295
- extid = model.derive_external_identifier
296
- puts "Generator: #{model.object_identifier_generator_type}"
297
-
298
- # Test VerifiableIdentifier (if using)
299
- vid = model.generate_verifiable_id
300
- puts "Verifiable: #{YourModel.verified_identifier?(vid)}"
301
-
302
- # Test scoped identifiers (if using)
303
- scoped_vid = model.generate_verifiable_id(scope: 'production')
304
- puts "Scoped valid: #{YourModel.verified_identifier?(scoped_vid, scope: 'production')}"
305
- puts "Wrong scope: #{YourModel.verified_identifier?(scoped_vid, scope: 'development')}"
306
- ```
@@ -1,95 +0,0 @@
1
- # Migrating Guide: v2.0.0-pre13
2
-
3
- This version introduces the Feature Autoloading System for automatic discovery and loading of feature-specific configuration files, enabling cleaner separation between core model definitions and feature configurations.
4
-
5
- ## Feature Autoloading System
6
-
7
- ### What Changed
8
-
9
- Features now automatically discover and load extension files from your project directories using conventional file naming patterns. This eliminates the need to configure features in your main model files.
10
-
11
- ### Basic Migration
12
-
13
- #### Before
14
- ```ruby
15
- # app/models/user.rb
16
- class User < Familia::Horreum
17
- field :name, :email, :password
18
-
19
- feature :safe_dump
20
- safe_dump_fields :name, :email # Configuration mixed with model
21
- end
22
- ```
23
-
24
- #### After
25
- ```ruby
26
- # app/models/user.rb - Clean model definition
27
- class User < Familia::Horreum
28
- field :name, :email, :password
29
- feature :safe_dump # Configuration auto-loaded
30
- end
31
-
32
- # app/models/user/safe_dump_extensions.rb - Automatically discovered
33
- class User
34
- safe_dump_fields :name, :email
35
- end
36
- ```
37
-
38
- ### File Naming Convention
39
-
40
- Extension files follow the pattern: `{model_name}/{feature_name}_*.rb`
41
-
42
- ```
43
- app/models/
44
- ├── user.rb
45
- ├── user/
46
- │ ├── safe_dump_extensions.rb # SafeDump configuration
47
- │ └── expiration_config.rb # Expiration settings
48
- ```
49
-
50
- ### Migration Steps
51
-
52
- 1. **Create extension directories**: `mkdir -p app/models/user`
53
- 2. **Extract feature configuration** from main model files to separate extension files
54
- 3. **Verify autoloading**: Check that feature methods are available after migration
55
-
56
- ### Debugging
57
-
58
- Enable debug output to troubleshoot autoloading:
59
- ```ruby
60
- ENV['FAMILIA_DEBUG'] = '1' # Shows discovered and loaded files
61
- ```
62
-
63
- Common issues:
64
- - Files must follow `{feature_name}_*.rb` naming pattern
65
- - Extension files should reopen the same class as your model
66
-
67
- ## Architecture
68
-
69
- The Feature Autoloading System consists of two key components:
70
-
71
- ### Familia::Features::Autoloader
72
- A utility module providing shared file loading functionality:
73
- - Handles Dir.glob pattern matching and file loading
74
- - Provides consistent debug logging across all autoloading scenarios
75
- - Used by both feature-specific and general-purpose autoloading
76
-
77
- ### Familia::Features::Autoloadable
78
- A mixin for feature modules that enables post-inclusion autoloading:
79
- - Uses `Module.const_source_location` to find where user classes are defined
80
- - Discovers extension files using conventional patterns relative to the user class location
81
- - Integrates with the feature system's inclusion lifecycle
82
-
83
- When you call `feature :safe_dump`, the SafeDump module (which includes Autoloadable) triggers post-inclusion autoloading that searches for `user/safe_dump_*.rb` files and loads them automatically.
84
-
85
- ## New Capabilities
86
-
87
- 1. **Automatic Extension Discovery**: No manual require statements needed
88
- 2. **Conventional File Organization**: Standard patterns for consistent project structure
89
- 3. **Feature Isolation**: Clean separation between core models and feature configurations
90
- 4. **Shared Autoloader Infrastructure**: Consistent loading behavior across all features
91
- 5. **Debug Support**: Built-in debugging for troubleshooting autoloading issues
92
-
93
- ## Breaking Changes
94
-
95
- **None** - This release is fully backward compatible. Existing models and feature configurations continue to work without modification.
@@ -1,37 +0,0 @@
1
- # Migrating Guide: v2.0.0-pre14
2
-
3
- This version renames TimeUtils to TimeLiterals for semantic clarity and fixes an ExternalIdentifier bug.
4
-
5
- ## Breaking Change: TimeUtils → TimeLiterals
6
-
7
- **Migration Required:**
8
-
9
- Find and replace in your codebase:
10
- ```bash
11
- # Find files to update
12
- grep -r "using Familia::Refinements::TimeUtils" .
13
-
14
- # Replace the import
15
- sed -i 's/using Familia::Refinements::TimeUtils/using Familia::Refinements::TimeLiterals/g' *.rb
16
- ```
17
-
18
- **Before:**
19
- ```ruby
20
- using Familia::Refinements::TimeUtils
21
- ```
22
-
23
- **After:**
24
- ```ruby
25
- using Familia::Refinements::TimeLiterals
26
- ```
27
-
28
- All functionality remains identical - only the module name changed.
29
-
30
- ## Bug Fix: ExternalIdentifier
31
-
32
- Fixed `NoMethodError` when using ExternalIdentifier by replacing incorrect `.del()` calls with `.remove_field()` in HashKey operations. This affected:
33
- - Changing external identifier values
34
- - Looking up objects by external ID
35
- - Destroying objects with external identifiers
36
-
37
- No migration needed - the fix is automatic.
@@ -1,58 +0,0 @@
1
- # Migrating Guide: v2.0.0-pre18
2
-
3
- This version completes the JSON serialization implementation by removing the string-as-is optimization and fixes encrypted field visibility in serialization.
4
-
5
- ## JSON Serialization for All Types
6
-
7
- **What Changed:**
8
-
9
- All field values (including strings) are now JSON-encoded during storage for consistent type preservation.
10
-
11
- **Storage Format:**
12
-
13
- ```ruby
14
- # Before (v2.0.0-pre14):
15
- HGET user:123 name
16
- "John Doe" # Plain string
17
-
18
- # After (v2.0.0-pre18):
19
- HGET user:123 name
20
- "\"John Doe\"" # JSON-encoded string
21
- ```
22
-
23
- **Migration:**
24
-
25
- No migration needed. The deserializer automatically handles:
26
- - **New format**: `"\"value\""` → `"value"`
27
- - **Legacy format**: `"value"` → `"value"`
28
-
29
- **Why This Matters:**
30
-
31
- Prevents data corruption for edge cases:
32
- - Badge `"007"` stays `"007"` (not converted to integer `7`)
33
- - String `"true"` stays `"true"` (not converted to boolean)
34
- - String `"null"` stays `"null"` (not converted to `nil`)
35
-
36
- ## Encrypted Field Security Fix
37
-
38
- **What Changed:**
39
-
40
- Fields defined with `category: :encrypted` now correctly exclude encrypted data from `to_h()` output.
41
-
42
- **Before:**
43
- ```ruby
44
- field :secret, category: :encrypted
45
- user.to_h # => {"id" => "123", "secret" => {...}} # ❌ Exposed!
46
- ```
47
-
48
- **After:**
49
- ```ruby
50
- field :secret, category: :encrypted
51
- user.to_h # => {"id" => "123"} # ✅ Secure
52
- ```
53
-
54
- Both `encrypted_field` and `field :name, category: :encrypted` now behave identically for security.
55
-
56
- **Migration:**
57
-
58
- No code changes needed. Review any code relying on encrypted fields appearing in `to_h()` output.
@@ -1,197 +0,0 @@
1
- # Migrating Guide: v2.0.0-pre19
2
-
3
- This version introduces significant improvements to Familia's database operations, making them more atomic, reliable, and consistent with Rails conventions. The changes include enhanced error handling, optimistic locking support, and breaking API changes.
4
-
5
- ## Breaking Changes
6
-
7
- ### Management.create → create!
8
-
9
- **What Changed:**
10
-
11
- The `create` class method has been renamed to `create!` to follow Rails conventions and indicate that it raises exceptions on failure.
12
-
13
- **Before:**
14
- ```ruby
15
- user = User.create(email: "test@example.com")
16
- ```
17
-
18
- **After:**
19
- ```ruby
20
- user = User.create!(email: "test@example.com")
21
- ```
22
-
23
- **Why This Matters:**
24
-
25
- - Follows Rails naming conventions where `!` methods raise exceptions
26
- - Makes it clear that `CreationError` will be raised if object already exists
27
- - Prevents silent failures in object creation
28
-
29
- ### save_if_not_exists → save_if_not_exists!
30
-
31
- **What Changed:**
32
-
33
- The `save_if_not_exists` method has been renamed to `save_if_not_exists!` and now includes optimistic locking with automatic retry logic.
34
-
35
- **Before:**
36
- ```ruby
37
- success = user.save_if_not_exists
38
- ```
39
-
40
- **After:**
41
- ```ruby
42
- success = user.save_if_not_exists!
43
- ```
44
-
45
- **Behavior Changes:**
46
- - Now uses Redis WATCH/MULTI/EXEC for optimistic locking
47
- - Automatically retries up to 3 times on `OptimisticLockError`
48
- - Raises `RecordExistsError` if object already exists
49
- - More atomic and thread-safe
50
-
51
- ## Enhanced Error Handling
52
-
53
- ### New Error Hierarchy
54
-
55
- **What's New:**
56
-
57
- A structured error hierarchy provides better error categorization:
58
-
59
- ```ruby
60
- Familia::Problem # Base class
61
- ├── Familia::PersistenceError # Redis/database errors
62
- │ ├── Familia::NonUniqueKey
63
- │ ├── Familia::OptimisticLockError
64
- │ ├── Familia::OperationModeError
65
- │ ├── Familia::NoConnectionAvailable
66
- │ ├── Familia::NotFound
67
- │ └── Familia::NotConnected
68
- └── Familia::HorreumError # Model-related errors
69
- ├── Familia::CreationError
70
- ├── Familia::NoIdentifier
71
- ├── Familia::FieldTypeError
72
- ├── Familia::AutoloadError
73
- ├── Familia::SerializerError
74
- ├── Familia::UnknownFieldError
75
- └── Familia::NotDistinguishableError
76
- ```
77
-
78
- **Migration:**
79
-
80
- Update exception handling to use specific error classes:
81
-
82
- ```ruby
83
- # Before
84
- rescue Familia::Problem => e
85
- # Handle all errors
86
-
87
- # After - More granular handling
88
- rescue Familia::CreationError => e
89
- # Handle creation failures specifically
90
- rescue Familia::OptimisticLockError => e
91
- # Handle concurrent modification
92
- rescue Familia::PersistenceError => e
93
- # Handle database-related errors
94
- ```
95
-
96
- ### New Exception Types
97
-
98
- - **`CreationError`**: Raised when object creation fails (replaces generic errors)
99
- - **`OptimisticLockError`**: Raised when WATCH fails due to concurrent modification
100
- - **`UnknownFieldError`**: Raised when referencing non-existent fields
101
-
102
- ## Database Operation Improvements
103
-
104
- ### Atomic Save Operations
105
-
106
- **What Changed:**
107
-
108
- The `save` method now uses a single Redis transaction for complete atomicity:
109
-
110
- ```ruby
111
- # All operations now happen atomically:
112
- # 1. Save all fields (HMSET)
113
- # 2. Set expiration (EXPIRE)
114
- # 3. Update indexes
115
- # 4. Add to instances collection
116
- ```
117
-
118
- **Benefits:**
119
- - Eliminates race conditions during save operations
120
- - Ensures data consistency across related operations
121
- - Better performance with fewer round trips
122
-
123
- ### Enhanced Timestamp Precision
124
-
125
- **What Changed:**
126
-
127
- Created/updated timestamps now use float values instead of integers for higher precision:
128
-
129
- **Before:**
130
- ```ruby
131
- user.created # => 1697234567 (integer seconds)
132
- ```
133
-
134
- **After:**
135
- ```ruby
136
- user.created # => 1697234567.123 (float with milliseconds)
137
- ```
138
-
139
- **Migration:**
140
-
141
- No code changes needed. Existing integer timestamps continue to work.
142
-
143
- ## Redis Command Enhancements
144
-
145
- ### New Commands Available
146
-
147
- Added support for optimistic locking commands:
148
- - `watch(key)` - Watch key for changes
149
- - `unwatch()` - Remove all watches
150
- - `discard()` - Discard queued commands
151
-
152
- ### Improved Command Logging
153
-
154
- Database command logging now includes:
155
- - Structured format for better readability
156
- - Pipelined operation tracking
157
- - Transaction boundary markers
158
- - Command timing information
159
-
160
- ## Terminology Updates
161
-
162
- **What Changed:**
163
-
164
- Standardized on "pipelined" terminology throughout (previously mixed "pipeline"/"pipelined").
165
-
166
- **Files Affected:**
167
- - Method names now consistently use "pipelined"
168
- - Documentation updated to match Redis terminology
169
- - Log messages standardized
170
-
171
- **Migration:**
172
-
173
- No code changes needed - this was an internal consistency improvement.
174
-
175
- ## Recommended Actions
176
-
177
- 1. **Update method calls:**
178
- ```ruby
179
- # Replace all instances
180
- Model.create(...) → Model.create!(...)
181
- obj.save_if_not_exists → obj.save_if_not_exists!
182
- ```
183
-
184
- 2. **Review error handling:**
185
- ```ruby
186
- # Consider more specific error handling
187
- rescue Familia::CreationError
188
- rescue Familia::OptimisticLockError
189
- ```
190
-
191
- 3. **Test concurrent operations:**
192
- - The new optimistic locking provides better concurrency handling
193
- - Verify your application handles `OptimisticLockError` appropriately
194
-
195
- 4. **Review logging:**
196
- - Enhanced database command logging may affect log volume
197
- - Adjust log levels if needed for production environments