familia 2.0.0.pre4 → 2.0.0.pre6

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.
Files changed (178) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop_todo.yml +17 -17
  4. data/CLAUDE.md +11 -8
  5. data/Gemfile +5 -1
  6. data/Gemfile.lock +19 -3
  7. data/README.md +36 -157
  8. data/docs/overview.md +359 -0
  9. data/docs/wiki/API-Reference.md +347 -0
  10. data/docs/wiki/Connection-Pooling-Guide.md +437 -0
  11. data/docs/wiki/Encrypted-Fields-Overview.md +101 -0
  12. data/docs/wiki/Expiration-Feature-Guide.md +596 -0
  13. data/docs/wiki/Feature-System-Guide.md +600 -0
  14. data/docs/wiki/Features-System-Developer-Guide.md +892 -0
  15. data/docs/wiki/Field-System-Guide.md +784 -0
  16. data/docs/wiki/Home.md +106 -0
  17. data/docs/wiki/Implementation-Guide.md +276 -0
  18. data/docs/wiki/Quantization-Feature-Guide.md +721 -0
  19. data/docs/wiki/RelatableObjects-Guide.md +563 -0
  20. data/docs/wiki/Security-Model.md +183 -0
  21. data/docs/wiki/Transient-Fields-Guide.md +280 -0
  22. data/lib/familia/base.rb +18 -27
  23. data/lib/familia/connection.rb +6 -5
  24. data/lib/familia/{datatype → data_type}/commands.rb +2 -5
  25. data/lib/familia/{datatype → data_type}/serialization.rb +8 -10
  26. data/lib/familia/data_type/types/counter.rb +38 -0
  27. data/lib/familia/{datatype → data_type}/types/hashkey.rb +20 -2
  28. data/lib/familia/{datatype → data_type}/types/list.rb +17 -18
  29. data/lib/familia/data_type/types/lock.rb +43 -0
  30. data/lib/familia/{datatype → data_type}/types/sorted_set.rb +17 -17
  31. data/lib/familia/{datatype → data_type}/types/string.rb +11 -3
  32. data/lib/familia/{datatype → data_type}/types/unsorted_set.rb +17 -18
  33. data/lib/familia/{datatype.rb → data_type.rb} +12 -14
  34. data/lib/familia/encryption/encrypted_data.rb +137 -0
  35. data/lib/familia/encryption/manager.rb +119 -0
  36. data/lib/familia/encryption/provider.rb +49 -0
  37. data/lib/familia/encryption/providers/aes_gcm_provider.rb +123 -0
  38. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +184 -0
  39. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +138 -0
  40. data/lib/familia/encryption/registry.rb +50 -0
  41. data/lib/familia/encryption.rb +178 -0
  42. data/lib/familia/encryption_request_cache.rb +68 -0
  43. data/lib/familia/errors.rb +17 -3
  44. data/lib/familia/features/encrypted_fields/concealed_string.rb +295 -0
  45. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +221 -0
  46. data/lib/familia/features/encrypted_fields.rb +28 -0
  47. data/lib/familia/features/expiration.rb +107 -77
  48. data/lib/familia/features/quantization.rb +5 -9
  49. data/lib/familia/features/relatable_objects.rb +2 -4
  50. data/lib/familia/features/safe_dump.rb +14 -17
  51. data/lib/familia/features/transient_fields/redacted_string.rb +159 -0
  52. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +62 -0
  53. data/lib/familia/features/transient_fields/transient_field_type.rb +139 -0
  54. data/lib/familia/features/transient_fields.rb +47 -0
  55. data/lib/familia/features.rb +40 -24
  56. data/lib/familia/field_type.rb +273 -0
  57. data/lib/familia/horreum/{connection.rb → core/connection.rb} +6 -15
  58. data/lib/familia/horreum/{commands.rb → core/database_commands.rb} +20 -21
  59. data/lib/familia/horreum/core/serialization.rb +535 -0
  60. data/lib/familia/horreum/{utils.rb → core/utils.rb} +9 -12
  61. data/lib/familia/horreum/core.rb +21 -0
  62. data/lib/familia/horreum/{settings.rb → shared/settings.rb} +10 -4
  63. data/lib/familia/horreum/subclass/definition.rb +469 -0
  64. data/lib/familia/horreum/{class_methods.rb → subclass/management.rb} +27 -250
  65. data/lib/familia/horreum/{related_fields_management.rb → subclass/related_fields_management.rb} +15 -10
  66. data/lib/familia/horreum.rb +30 -22
  67. data/lib/familia/logging.rb +14 -14
  68. data/lib/familia/settings.rb +39 -3
  69. data/lib/familia/utils.rb +45 -0
  70. data/lib/familia/version.rb +1 -1
  71. data/lib/familia.rb +3 -2
  72. data/try/core/base_enhancements_try.rb +115 -0
  73. data/try/core/connection_try.rb +0 -1
  74. data/try/core/create_method_try.rb +240 -0
  75. data/try/core/database_consistency_try.rb +299 -0
  76. data/try/core/errors_try.rb +25 -5
  77. data/try/core/familia_extended_try.rb +3 -4
  78. data/try/core/familia_try.rb +1 -2
  79. data/try/core/persistence_operations_try.rb +297 -0
  80. data/try/core/pools_try.rb +2 -2
  81. data/try/core/secure_identifier_try.rb +0 -1
  82. data/try/core/settings_try.rb +0 -1
  83. data/try/core/utils_try.rb +0 -1
  84. data/try/{datatypes → data_types}/boolean_try.rb +1 -2
  85. data/try/data_types/counter_try.rb +93 -0
  86. data/try/{datatypes → data_types}/datatype_base_try.rb +2 -3
  87. data/try/{datatypes → data_types}/hash_try.rb +1 -2
  88. data/try/{datatypes → data_types}/list_try.rb +1 -2
  89. data/try/data_types/lock_try.rb +133 -0
  90. data/try/{datatypes → data_types}/set_try.rb +1 -2
  91. data/try/{datatypes → data_types}/sorted_set_try.rb +1 -2
  92. data/try/{datatypes → data_types}/string_try.rb +1 -2
  93. data/try/debugging/README.md +32 -0
  94. data/try/debugging/cache_behavior_tracer.rb +91 -0
  95. data/try/debugging/debug_aad_process.rb +82 -0
  96. data/try/debugging/debug_concealed_internal.rb +59 -0
  97. data/try/debugging/debug_concealed_reveal.rb +61 -0
  98. data/try/debugging/debug_context_aad.rb +68 -0
  99. data/try/debugging/debug_context_simple.rb +80 -0
  100. data/try/debugging/debug_cross_context.rb +62 -0
  101. data/try/debugging/debug_database_load.rb +64 -0
  102. data/try/debugging/debug_encrypted_json_check.rb +53 -0
  103. data/try/debugging/debug_encrypted_json_step_by_step.rb +62 -0
  104. data/try/debugging/debug_exists_lifecycle.rb +54 -0
  105. data/try/debugging/debug_field_decrypt.rb +74 -0
  106. data/try/debugging/debug_fresh_cross_context.rb +73 -0
  107. data/try/debugging/debug_load_path.rb +66 -0
  108. data/try/debugging/debug_method_definition.rb +46 -0
  109. data/try/debugging/debug_method_resolution.rb +41 -0
  110. data/try/debugging/debug_minimal.rb +24 -0
  111. data/try/debugging/debug_provider.rb +68 -0
  112. data/try/debugging/debug_secure_behavior.rb +73 -0
  113. data/try/debugging/debug_string_class.rb +46 -0
  114. data/try/debugging/debug_test.rb +46 -0
  115. data/try/debugging/debug_test_design.rb +80 -0
  116. data/try/debugging/encryption_method_tracer.rb +138 -0
  117. data/try/debugging/provider_diagnostics.rb +110 -0
  118. data/try/edge_cases/hash_symbolization_try.rb +0 -1
  119. data/try/edge_cases/json_serialization_try.rb +0 -1
  120. data/try/edge_cases/reserved_keywords_try.rb +42 -11
  121. data/try/encryption/config_persistence_try.rb +192 -0
  122. data/try/encryption/encryption_core_try.rb +328 -0
  123. data/try/encryption/instance_variable_scope_try.rb +31 -0
  124. data/try/encryption/module_loading_try.rb +28 -0
  125. data/try/encryption/providers/aes_gcm_provider_try.rb +178 -0
  126. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +169 -0
  127. data/try/encryption/roundtrip_validation_try.rb +28 -0
  128. data/try/encryption/secure_memory_handling_try.rb +125 -0
  129. data/try/features/encrypted_fields_core_try.rb +125 -0
  130. data/try/features/encrypted_fields_integration_try.rb +216 -0
  131. data/try/features/encrypted_fields_no_cache_security_try.rb +219 -0
  132. data/try/features/encrypted_fields_security_try.rb +377 -0
  133. data/try/features/encryption_fields/aad_protection_try.rb +138 -0
  134. data/try/features/encryption_fields/concealed_string_core_try.rb +250 -0
  135. data/try/features/encryption_fields/context_isolation_try.rb +141 -0
  136. data/try/features/encryption_fields/error_conditions_try.rb +116 -0
  137. data/try/features/encryption_fields/fresh_key_derivation_try.rb +128 -0
  138. data/try/features/encryption_fields/fresh_key_try.rb +168 -0
  139. data/try/features/encryption_fields/key_rotation_try.rb +123 -0
  140. data/try/features/encryption_fields/memory_security_try.rb +37 -0
  141. data/try/features/encryption_fields/missing_current_key_version_try.rb +23 -0
  142. data/try/features/encryption_fields/nonce_uniqueness_try.rb +56 -0
  143. data/try/features/encryption_fields/secure_by_default_behavior_try.rb +310 -0
  144. data/try/features/encryption_fields/thread_safety_try.rb +199 -0
  145. data/try/features/encryption_fields/universal_serialization_safety_try.rb +174 -0
  146. data/try/features/expiration_try.rb +0 -1
  147. data/try/features/feature_dependencies_try.rb +159 -0
  148. data/try/features/quantization_try.rb +0 -1
  149. data/try/features/real_feature_integration_try.rb +148 -0
  150. data/try/features/relatable_objects_try.rb +0 -1
  151. data/try/features/safe_dump_advanced_try.rb +0 -1
  152. data/try/features/safe_dump_try.rb +0 -1
  153. data/try/features/transient_fields/redacted_string_try.rb +248 -0
  154. data/try/features/transient_fields/refresh_reset_try.rb +164 -0
  155. data/try/features/transient_fields/simple_refresh_test.rb +50 -0
  156. data/try/features/transient_fields/single_use_redacted_string_try.rb +310 -0
  157. data/try/features/transient_fields_core_try.rb +181 -0
  158. data/try/features/transient_fields_integration_try.rb +260 -0
  159. data/try/helpers/test_helpers.rb +67 -0
  160. data/try/horreum/base_try.rb +157 -3
  161. data/try/horreum/enhanced_conflict_handling_try.rb +176 -0
  162. data/try/horreum/field_categories_try.rb +118 -0
  163. data/try/horreum/field_definition_try.rb +96 -0
  164. data/try/horreum/initialization_try.rb +1 -2
  165. data/try/horreum/relations_try.rb +1 -2
  166. data/try/horreum/serialization_persistent_fields_try.rb +165 -0
  167. data/try/horreum/serialization_try.rb +41 -7
  168. data/try/memory/memory_basic_test.rb +73 -0
  169. data/try/memory/memory_detailed_test.rb +121 -0
  170. data/try/memory/memory_docker_ruby_dump.sh +80 -0
  171. data/try/memory/memory_search_for_string.rb +83 -0
  172. data/try/memory/test_actual_redactedstring_protection.rb +38 -0
  173. data/try/models/customer_safe_dump_try.rb +1 -2
  174. data/try/models/customer_try.rb +1 -2
  175. data/try/models/datatype_base_try.rb +1 -2
  176. data/try/models/familia_object_try.rb +0 -1
  177. metadata +131 -23
  178. data/lib/familia/horreum/serialization.rb +0 -445
data/docs/overview.md ADDED
@@ -0,0 +1,359 @@
1
+
2
+ # Familia - Overview
3
+
4
+ > [!NOTE]
5
+ > This document refers to Valkey throughout, but all examples and patterns work identically with Redis. Familia supports both Valkey and Redis as they share the same protocol and data structures.
6
+
7
+ ## Introduction
8
+
9
+ Familia is a Ruby ORM for Valkey (Redis) that provides object-oriented access to Valkey's native data structures. Unlike traditional ORMs that map objects to relational tables, Familia preserves Valkey's performance and flexibility while offering a familiar Ruby interface.
10
+
11
+ **Why Familia?**
12
+ - Maps Ruby objects directly to Valkey's native data structures (strings, lists, sets, etc.)
13
+ - Maintains Valkey's atomic operations and performance characteristics
14
+ - Handles complex patterns (quantization, encryption, expiration) out of the box
15
+
16
+ ## Core Concepts
17
+
18
+ ### What is a Horreum Class?
19
+
20
+ The ```Horreum``` class is Familia's foundation, representing Valkey-compatible objects. It's named after ancient Roman storehouses, reflecting its purpose as a structured data repository.
21
+
22
+ ```ruby
23
+ class Flower < Familia::Horreum
24
+ identifier_field :token
25
+ field :name
26
+ field :color
27
+ field :species
28
+ list :owners
29
+ set :tags
30
+ zset :metrics
31
+ hashkey :props
32
+ string :counter
33
+ end
34
+ ```
35
+
36
+ This pattern lets you work with Valkey data as Ruby objects while maintaining direct access to Valkey's native operations.
37
+
38
+ ### Flexible Identifiers
39
+
40
+ Horreum classes require identifiers to determine Valkey key names. You can define them in various ways:
41
+
42
+ ```ruby
43
+ class User < Familia::Horreum
44
+ # Simple field-based identifier
45
+ identifier_field :email
46
+
47
+ # Computed identifier
48
+ identifier_field ->(user) { "user:#{user.id}" }
49
+
50
+ # Multi-field composite identifier
51
+ identifier_field [:type, :email]
52
+
53
+ field :email
54
+ field :type
55
+ field :id
56
+ end
57
+ ```
58
+
59
+ This flexibility allows you to adapt to different Valkey key naming strategies while maintaining clean Ruby object interfaces.
60
+
61
+ ### Data Types Mapping
62
+
63
+ Familia provides direct mappings to Valkey's native data structures:
64
+
65
+ ```ruby
66
+ class Product < Familia::Horreum
67
+ identifier_field :sku
68
+
69
+ # Basic fields
70
+ field :sku
71
+ field :name
72
+ field :price
73
+
74
+ # String fields (for counters, simple values)
75
+ string :view_count, default: '0'
76
+ # Usage: view_count.increment (atomic increment)
77
+
78
+ # Lists (ordered, allows duplicates)
79
+ list :categories
80
+ # Usage: categories.push('fruit'), categories.pop
81
+
82
+ # Sets (unordered, unique)
83
+ set :tags
84
+ # Usage: tags.add('organic'), tags.include?('organic')
85
+
86
+ # Sorted sets (scored, ordered)
87
+ zset :ratings
88
+ # Usage: ratings.add(4.5, 'customer123'), ratings.rank('customer123')
89
+
90
+ # Hash keys (dictionaries)
91
+ hashkey :attributes
92
+ # Usage: attributes['color'] = 'red', attributes.to_h
93
+ end
94
+ ```
95
+
96
+ Each type maintains Valkey's native operations while providing Ruby-friendly interfaces.
97
+
98
+ ## Essential Features
99
+
100
+ ### Automatic Expiration
101
+
102
+ Set default TTL for objects that should expire:
103
+
104
+ ```ruby
105
+ class Session < Familia::Horreum
106
+ feature :expiration
107
+ default_expiration 30.minutes
108
+
109
+ field :user_id
110
+ field :token
111
+ end
112
+
113
+ # Auto-expires in 30 minutes
114
+ session = Session.create(user_id: '123', token: 'abc')
115
+ ```
116
+
117
+ This is ideal for temporary data like authentication tokens or cache entries.
118
+
119
+ ### Safe Dumping for APIs
120
+
121
+ Control which fields are exposed when serializing objects:
122
+
123
+ ```ruby
124
+ class User < Familia::Horreum
125
+ feature :safe_dump
126
+
127
+ @safe_dump_fields = [
128
+ :id,
129
+ :email,
130
+ {full_name: ->(user) { "#{user.first_name} #{user.last_name}" }}
131
+ ]
132
+
133
+ field :id, :email, :first_name, :last_name, :password_hash
134
+ end
135
+
136
+ user.safe_dump
137
+ #=> {id: "123", email: "alice@example.com", full_name: "Alice Smith"}
138
+ ```
139
+
140
+ Prevents accidental exposure of sensitive data in API responses.
141
+
142
+ ### Time-based Quantization
143
+
144
+ Group time-based metrics into buckets:
145
+
146
+ ```ruby
147
+ class DailyMetric < Familia::Horreum
148
+ feature :quantization
149
+ string :counter, default_expiration: 1.day, quantize: [10.minutes, '%H:%M']
150
+ end
151
+ ```
152
+
153
+ This automatically groups metrics into 10-minute intervals formatted as "HH:MM", ideal for analytics dashboards.
154
+
155
+ ## Advanced Patterns
156
+
157
+ ### Custom Methods and Logic
158
+
159
+ Add domain-specific behavior to your models:
160
+
161
+ ```ruby
162
+ class User < Familia::Horreum
163
+ field :first_name
164
+ field :last_name
165
+ field :status
166
+
167
+ def full_name
168
+ "#{first_name} #{last_name}"
169
+ end
170
+
171
+ def active?
172
+ status == 'active'
173
+ end
174
+ end
175
+ ```
176
+
177
+ These methods work alongside Familia's persistence layer, letting you build rich domain models.
178
+
179
+ ### Transactional Operations
180
+
181
+ Execute multiple Valkey commands atomically:
182
+
183
+ ```ruby
184
+ user.transaction do |conn|
185
+ conn.set("user:#{user.id}:status", "active")
186
+ conn.zadd("active_users", Time.now.to_i, user.id)
187
+ end
188
+ ```
189
+
190
+ Preserves data integrity for complex operations that require multiple Valkey commands.
191
+
192
+ ### Connection Management and Pooling
193
+
194
+ Configure connection pooling for production environments:
195
+
196
+ ```ruby
197
+ require 'connection_pool'
198
+
199
+ pools = {
200
+ "redis://localhost:6379/0" => ConnectionPool.new(size: 10) { Redis.new(db: 0) }
201
+ }
202
+
203
+ Familia.connection_provider = lambda do |uri|
204
+ pool = pools[uri]
205
+ pool.with { |conn| conn }
206
+ end
207
+ ```
208
+
209
+ This ensures efficient Valkey connection usage in multi-threaded applications.
210
+
211
+ ### Encrypted Fields
212
+
213
+ Protect sensitive data at rest:
214
+
215
+ ```ruby
216
+ class SecureUser < Familia::Horreum
217
+ feature :encrypted_fields
218
+
219
+ identifier_field :id
220
+ field :id
221
+ field :email # Plain text
222
+ encrypted_field :ssn # Encrypted
223
+ encrypted_field :notes, aad_fields: [:id, :email] # With auth data
224
+ end
225
+ ```
226
+
227
+ Uses authenticated encryption to protect data while allowing selective field access.
228
+
229
+ ### Open-ended Serialization
230
+
231
+ Customize how objects are serialized to Valkey:
232
+
233
+ ```ruby
234
+ class JsonModel < Familia::Horreum
235
+ def serialize_value
236
+ JSON.generate(to_h)
237
+ end
238
+
239
+ def self.deserialize_value(data)
240
+ new(**JSON.parse(data, symbolize_names: true))
241
+ end
242
+ end
243
+ ```
244
+
245
+ Enables integration with custom serialization formats beyond Familia's defaults.
246
+
247
+ ## Configuration
248
+
249
+ ### Basic Setup
250
+
251
+ ```ruby
252
+ # Simple connection
253
+ Familia.uri = 'redis://localhost:6379/0'
254
+
255
+ # Multiple databases
256
+ Familia.redis_config = {
257
+ host: 'localhost',
258
+ port: 6379,
259
+ db: 0,
260
+ timeout: 5
261
+ }
262
+ ```
263
+
264
+ ### Encryption Setup (Optional)
265
+
266
+ ```ruby
267
+ # Generate base64-encoded 32-byte keys
268
+ Familia.config.encryption_keys = {
269
+ v1: Base64.strict_encode64(SecureRandom.bytes(32)),
270
+ v2: Base64.strict_encode64(SecureRandom.bytes(32))
271
+ }
272
+ Familia.config.current_key_version = :v2
273
+ ```
274
+
275
+ ## Common Patterns
276
+
277
+ ### Bulk Operations
278
+
279
+ ```ruby
280
+ # Load multiple objects
281
+ users = User.multiget('alice@example.com', 'bob@example.com')
282
+
283
+ # Batch operations
284
+ User.transaction do |conn|
285
+ conn.set('user:alice:status', 'active')
286
+ conn.zadd('active_users', Time.now.to_i, 'alice')
287
+ end
288
+ ```
289
+
290
+ ### Error Handling
291
+
292
+ ```ruby
293
+ begin
294
+ user = User.load('nonexistent@example.com')
295
+ rescue Familia::Problem => e
296
+ puts "User not found: #{e.message}"
297
+ end
298
+
299
+ # Safe loading
300
+ user = User.load('maybe@example.com') || User.new
301
+ ```
302
+
303
+ ## Troubleshooting
304
+
305
+ ### Common Issues
306
+
307
+ **Connection Errors:**
308
+ ```ruby
309
+ # Check connection
310
+ Familia.connect_to_uri('redis://localhost:6379/0')
311
+ ```
312
+
313
+ **Missing Keys:**
314
+ ```ruby
315
+ # Debug key names
316
+ user = User.new(email: 'test@example.com')
317
+ puts user.rediskey # Shows the Valkey key that would be used
318
+ ```
319
+
320
+ **Encryption Issues:**
321
+ ```ruby
322
+ # Validate encryption config
323
+ Familia::Encryption.validate_configuration!
324
+ ```
325
+
326
+ ### Debug Mode
327
+
328
+ ```ruby
329
+ # Enable debug logging
330
+ Familia.debug = true
331
+
332
+ # Check what's in Valkey
333
+ Familia.redis.keys('*') # List all keys (use carefully in production)
334
+ ```
335
+
336
+ ## Testing
337
+
338
+ ### Test Configuration
339
+
340
+ ```ruby
341
+ # test_helper.rb or spec_helper.rb
342
+ require 'familia'
343
+
344
+ # Use separate test database
345
+ Familia.uri = 'redis://localhost:6379/15'
346
+
347
+ # Setup encryption for tests
348
+ test_keys = {
349
+ v1: Base64.strict_encode64('a' * 32),
350
+ v2: Base64.strict_encode64('b' * 32)
351
+ }
352
+ Familia.config.encryption_keys = test_keys
353
+ Familia.config.current_key_version = :v1
354
+
355
+ # Clear data between tests
356
+ def clear_redis
357
+ Familia.redis.flushdb
358
+ end
359
+ ```
@@ -0,0 +1,347 @@
1
+ # API Reference
2
+
3
+ ## Class Methods
4
+
5
+ ### encrypted_field
6
+
7
+ Defines an encrypted field on a Familia::Horreum class.
8
+
9
+ ```ruby
10
+ encrypted_field(name, **options)
11
+ ```
12
+
13
+ **Parameters:**
14
+ - `name` (Symbol) - Field name
15
+ - `**options` (Hash) - Standard field options plus encryption-specific options
16
+
17
+ **Options:**
18
+ - `:as` - Custom accessor method name
19
+ - `:on_conflict` - Conflict resolution (always `:raise` for encrypted fields)
20
+
21
+ **Example:**
22
+ ```ruby
23
+ class User < Familia::Horreum
24
+ encrypted_field :favorite_snack
25
+ encrypted_field :api_key, as: :secret_key
26
+ end
27
+ ```
28
+
29
+ ### encrypted_fields
30
+
31
+ Returns list of encrypted field names.
32
+
33
+ ```ruby
34
+ User.encrypted_fields # => [:favorite_snack, :api_key]
35
+ ```
36
+
37
+ ## Instance Methods
38
+
39
+ ### Field Accessors
40
+
41
+ Encrypted fields provide standard accessors:
42
+
43
+ ```ruby
44
+ user.favorite_snack # Get decrypted value
45
+ user.favorite_snack = value # Set and encrypt value
46
+ user.favorite_snack! # Fast write (still encrypted)
47
+ ```
48
+
49
+ ### Passphrase-Protected Access
50
+
51
+ ```ruby
52
+ # For passphrase-protected fields
53
+ vault.secret_data(passphrase_value: "user_passphrase")
54
+ ```
55
+
56
+ ## Familia::Encryption Module
57
+
58
+ ### manager
59
+
60
+ Creates a manager instance with optional algorithm selection.
61
+
62
+ ```ruby
63
+ # Use best available provider
64
+ mgr = Familia::Encryption.manager
65
+
66
+ # Use specific algorithm
67
+ mgr = Familia::Encryption.manager(algorithm: 'xchacha20poly1305')
68
+ ```
69
+
70
+ ### encrypt
71
+
72
+ Encrypts plaintext using the default provider.
73
+
74
+ ```ruby
75
+ Familia::Encryption.encrypt(plaintext,
76
+ context: "User:favorite_snack:user123",
77
+ additional_data: nil
78
+ )
79
+ ```
80
+
81
+ ### encrypt_with
82
+
83
+ Encrypts plaintext with a specific algorithm.
84
+
85
+ ```ruby
86
+ Familia::Encryption.encrypt_with('aes-256-gcm', plaintext,
87
+ context: "User:favorite_snack:user123",
88
+ additional_data: nil
89
+ )
90
+ ```
91
+
92
+ ### decrypt
93
+
94
+ Decrypts ciphertext (auto-detects algorithm from JSON).
95
+
96
+ ```ruby
97
+ Familia::Encryption.decrypt(encrypted_json,
98
+ context: "User:favorite_snack:user123",
99
+ additional_data: nil
100
+ )
101
+ ```
102
+
103
+ ### status
104
+
105
+ Returns current encryption configuration and available providers.
106
+
107
+ ```ruby
108
+ Familia::Encryption.status
109
+ # => {
110
+ # default_algorithm: "xchacha20poly1305",
111
+ # available_algorithms: ["xchacha20poly1305", "aes-256-gcm"],
112
+ # preferred_available: "Familia::Encryption::Providers::XChaCha20Poly1305Provider",
113
+ # using_hardware: false,
114
+ # key_versions: [:v1],
115
+ # current_version: :v1
116
+ # }
117
+ ```
118
+
119
+ ### benchmark
120
+
121
+ Benchmarks available providers.
122
+
123
+ ```ruby
124
+ Familia::Encryption.benchmark(iterations: 1000)
125
+ # => {
126
+ # "xchacha20poly1305" => { time: 0.45, ops_per_sec: 4444, priority: 100 },
127
+ # "aes-256-gcm" => { time: 0.52, ops_per_sec: 3846, priority: 50 }
128
+ # }
129
+ ```
130
+
131
+ ### validate_configuration!
132
+
133
+ Validates encryption configuration at startup.
134
+
135
+ ```ruby
136
+ Familia::Encryption.validate_configuration!
137
+ # Raises Familia::EncryptionError if configuration invalid
138
+ ```
139
+
140
+ ### derivation_count / reset_derivation_count!
141
+
142
+ Monitors key derivation operations (for testing and debugging).
143
+
144
+ ```ruby
145
+ # Check how many key derivations have occurred
146
+ count = Familia::Encryption.derivation_count.value
147
+ # => 42
148
+
149
+ # Reset counter
150
+ Familia::Encryption.reset_derivation_count!
151
+ ```
152
+
153
+ ## Familia::Encryption::Manager
154
+
155
+ Low-level manager class for direct provider control.
156
+
157
+ ### initialize
158
+
159
+ ```ruby
160
+ # Use default provider
161
+ manager = Familia::Encryption::Manager.new
162
+
163
+ # Use specific algorithm
164
+ manager = Familia::Encryption::Manager.new(algorithm: 'aes-256-gcm')
165
+ ```
166
+
167
+ ### encrypt / decrypt
168
+
169
+ Same interface as module-level methods but tied to specific provider.
170
+
171
+ ## Familia::Encryption::Registry
172
+
173
+ Provider management system.
174
+
175
+ ### setup!
176
+
177
+ Registers all available providers.
178
+
179
+ ### get
180
+
181
+ Returns provider instance by algorithm name.
182
+
183
+ ### default_provider
184
+
185
+ Returns highest-priority available provider instance.
186
+
187
+ ### available_algorithms
188
+
189
+ Returns array of available algorithm names.
190
+
191
+ ## Configuration
192
+
193
+ ### Familia.configure
194
+
195
+ ```ruby
196
+ Familia.configure do |config|
197
+ # Single key configuration
198
+ config.encryption_keys = {
199
+ v1: ENV['FAMILIA_ENCRYPTION_KEY']
200
+ }
201
+ config.current_key_version = :v1
202
+
203
+ # Multi-version configuration
204
+ config.encryption_keys = {
205
+ v1_2024: ENV['OLD_KEY'],
206
+ v2_2025: ENV['NEW_KEY']
207
+ }
208
+ config.current_key_version = :v2_2025
209
+
210
+ # Key cache TTL (seconds)
211
+ config.key_cache_ttl = 300 # Default: 5 minutes
212
+ end
213
+ ```
214
+
215
+ ## Data Types
216
+
217
+ ### EncryptedData
218
+
219
+ Internal data structure for encrypted values.
220
+
221
+ ```ruby
222
+ EncryptedData = Data.define(
223
+ :library, # "libsodium" or "openssl"
224
+ :algorithm, # "xchacha20poly1305" or "aes-256-gcm"
225
+ :nonce, # Base64-encoded nonce/IV
226
+ :ciphertext, # Base64-encoded ciphertext
227
+ :key_version # Key version identifier
228
+ )
229
+ ```
230
+
231
+ ### RedactedString
232
+
233
+ String subclass that redacts sensitive data in output.
234
+
235
+ ```ruby
236
+ class RedactedString < String
237
+ def to_s
238
+ '[REDACTED]'
239
+ end
240
+
241
+ def inspect
242
+ '[REDACTED]'
243
+ end
244
+ end
245
+ ```
246
+
247
+ ## Exceptions
248
+
249
+ ### Familia::EncryptionError
250
+
251
+ Raised for encryption/decryption failures.
252
+
253
+ ```ruby
254
+ begin
255
+ user.decrypt_field(:favorite_snack)
256
+ rescue Familia::EncryptionError => e
257
+ case e.message
258
+ when /key version/
259
+ # Handle key version mismatch
260
+ when /authentication/
261
+ # Handle tampering
262
+ else
263
+ # Handle other errors
264
+ end
265
+ end
266
+ ```
267
+
268
+ ## CLI Commands
269
+
270
+ ### Generate Key
271
+
272
+ ```bash
273
+ $ familia encryption:generate_key [--bits 256]
274
+ # Outputs Base64-encoded key
275
+ ```
276
+
277
+ ### Verify Encryption
278
+
279
+ ```bash
280
+ $ familia encryption:verify [--model User] [--field favorite_snack]
281
+ # Verifies field encryption is working
282
+ ```
283
+
284
+ ### Rotate Keys
285
+
286
+ ```bash
287
+ $ familia encryption:rotate [--from v1] [--to v2]
288
+ # Migrates encrypted fields to new key
289
+ ```
290
+
291
+ ## Testing Helpers
292
+
293
+ ### EncryptionTestHelpers
294
+
295
+ ```ruby
296
+ module Familia::EncryptionTestHelpers
297
+ # Set up test encryption keys
298
+ def with_test_encryption_keys(&block)
299
+
300
+ # Verify field is encrypted in storage
301
+ def assert_field_encrypted(model, field)
302
+
303
+ # Verify decryption works
304
+ def assert_decryption_works(model, field, expected)
305
+ end
306
+ ```
307
+
308
+ ### RSpec Example
309
+
310
+ ```ruby
311
+ RSpec.describe User do
312
+ include Familia::EncryptionTestHelpers
313
+
314
+ it "encrypts favorite snack field" do
315
+ with_test_encryption_keys do
316
+ user = User.create(favorite_snack: "chocolate chip cookies")
317
+
318
+ assert_field_encrypted(user, :favorite_snack)
319
+ assert_decryption_works(user, :favorite_snack, "chocolate chip cookies")
320
+ end
321
+ end
322
+ end
323
+ ```
324
+
325
+ ## Performance Considerations
326
+
327
+ ### Key Derivation Caching
328
+
329
+ ```ruby
330
+ # Automatic in web requests
331
+ class ApplicationController
332
+ around_action :with_encryption_cache
333
+
334
+ def with_encryption_cache
335
+ Familia::Encryption.with_key_cache { yield }
336
+ end
337
+ end
338
+ ```
339
+
340
+ ### Batch Operations
341
+
342
+ ```ruby
343
+ # Efficient for bulk operations
344
+ User.batch_decrypt(:favorite_snack) do |users|
345
+ users.each { |u| process(u.favorite_snack) }
346
+ end
347
+ ```