familia 2.0.0.pre3 → 2.0.0.pre5

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop_todo.yml +17 -17
  4. data/CLAUDE.md +3 -3
  5. data/Gemfile +5 -1
  6. data/Gemfile.lock +18 -3
  7. data/README.md +36 -157
  8. data/TEST_COVERAGE.md +40 -0
  9. data/docs/overview.md +359 -0
  10. data/docs/wiki/API-Reference.md +270 -0
  11. data/docs/wiki/Encrypted-Fields-Overview.md +64 -0
  12. data/docs/wiki/Home.md +49 -0
  13. data/docs/wiki/Implementation-Guide.md +183 -0
  14. data/docs/wiki/Security-Model.md +143 -0
  15. data/lib/familia/base.rb +18 -27
  16. data/lib/familia/connection.rb +6 -5
  17. data/lib/familia/{datatype → data_type}/commands.rb +2 -5
  18. data/lib/familia/{datatype → data_type}/serialization.rb +8 -10
  19. data/lib/familia/{datatype → data_type}/types/hashkey.rb +2 -2
  20. data/lib/familia/{datatype → data_type}/types/list.rb +17 -18
  21. data/lib/familia/{datatype → data_type}/types/sorted_set.rb +17 -17
  22. data/lib/familia/{datatype → data_type}/types/string.rb +2 -1
  23. data/lib/familia/{datatype → data_type}/types/unsorted_set.rb +17 -18
  24. data/lib/familia/{datatype.rb → data_type.rb} +10 -12
  25. data/lib/familia/encryption/manager.rb +102 -0
  26. data/lib/familia/encryption/provider.rb +49 -0
  27. data/lib/familia/encryption/providers/aes_gcm_provider.rb +103 -0
  28. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +184 -0
  29. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +118 -0
  30. data/lib/familia/encryption/registry.rb +50 -0
  31. data/lib/familia/encryption.rb +178 -0
  32. data/lib/familia/encryption_request_cache.rb +68 -0
  33. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +153 -0
  34. data/lib/familia/features/encrypted_fields.rb +28 -0
  35. data/lib/familia/features/expiration.rb +107 -77
  36. data/lib/familia/features/quantization.rb +5 -9
  37. data/lib/familia/features/relatable_objects.rb +2 -4
  38. data/lib/familia/features/safe_dump.rb +14 -17
  39. data/lib/familia/features/transient_fields/redacted_string.rb +159 -0
  40. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +62 -0
  41. data/lib/familia/features/transient_fields/transient_field_type.rb +139 -0
  42. data/lib/familia/features/transient_fields.rb +47 -0
  43. data/lib/familia/features.rb +40 -24
  44. data/lib/familia/field_type.rb +270 -0
  45. data/lib/familia/horreum/connection.rb +8 -11
  46. data/lib/familia/horreum/{commands.rb → database_commands.rb} +7 -19
  47. data/lib/familia/horreum/definition_methods.rb +453 -0
  48. data/lib/familia/horreum/{class_methods.rb → management_methods.rb} +19 -229
  49. data/lib/familia/horreum/serialization.rb +46 -18
  50. data/lib/familia/horreum/settings.rb +10 -2
  51. data/lib/familia/horreum/utils.rb +9 -10
  52. data/lib/familia/horreum.rb +18 -10
  53. data/lib/familia/logging.rb +14 -14
  54. data/lib/familia/settings.rb +39 -3
  55. data/lib/familia/utils.rb +45 -0
  56. data/lib/familia/version.rb +1 -1
  57. data/lib/familia.rb +2 -1
  58. data/try/core/base_enhancements_try.rb +115 -0
  59. data/try/core/connection_try.rb +0 -1
  60. data/try/core/errors_try.rb +0 -1
  61. data/try/core/familia_extended_try.rb +3 -4
  62. data/try/core/familia_try.rb +0 -1
  63. data/try/core/pools_try.rb +2 -2
  64. data/try/core/secure_identifier_try.rb +0 -1
  65. data/try/core/settings_try.rb +0 -1
  66. data/try/core/utils_try.rb +0 -1
  67. data/try/{datatypes → data_types}/boolean_try.rb +1 -2
  68. data/try/{datatypes → data_types}/datatype_base_try.rb +2 -3
  69. data/try/{datatypes → data_types}/hash_try.rb +1 -2
  70. data/try/{datatypes → data_types}/list_try.rb +1 -2
  71. data/try/{datatypes → data_types}/set_try.rb +1 -2
  72. data/try/{datatypes → data_types}/sorted_set_try.rb +1 -2
  73. data/try/{datatypes → data_types}/string_try.rb +1 -2
  74. data/try/debugging/README.md +32 -0
  75. data/try/debugging/cache_behavior_tracer.rb +91 -0
  76. data/try/debugging/encryption_method_tracer.rb +138 -0
  77. data/try/debugging/provider_diagnostics.rb +110 -0
  78. data/try/edge_cases/hash_symbolization_try.rb +0 -1
  79. data/try/edge_cases/json_serialization_try.rb +0 -1
  80. data/try/edge_cases/reserved_keywords_try.rb +42 -11
  81. data/try/encryption/config_persistence_try.rb +192 -0
  82. data/try/encryption/encryption_core_try.rb +328 -0
  83. data/try/encryption/instance_variable_scope_try.rb +31 -0
  84. data/try/encryption/module_loading_try.rb +28 -0
  85. data/try/encryption/providers/aes_gcm_provider_try.rb +178 -0
  86. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +169 -0
  87. data/try/encryption/roundtrip_validation_try.rb +28 -0
  88. data/try/encryption/secure_memory_handling_try.rb +125 -0
  89. data/try/features/encrypted_fields_core_try.rb +117 -0
  90. data/try/features/encrypted_fields_integration_try.rb +220 -0
  91. data/try/features/encrypted_fields_no_cache_security_try.rb +205 -0
  92. data/try/features/encrypted_fields_security_try.rb +370 -0
  93. data/try/features/encryption_fields/aad_protection_try.rb +53 -0
  94. data/try/features/encryption_fields/context_isolation_try.rb +120 -0
  95. data/try/features/encryption_fields/error_conditions_try.rb +116 -0
  96. data/try/features/encryption_fields/fresh_key_derivation_try.rb +122 -0
  97. data/try/features/encryption_fields/fresh_key_try.rb +163 -0
  98. data/try/features/encryption_fields/key_rotation_try.rb +117 -0
  99. data/try/features/encryption_fields/memory_security_try.rb +37 -0
  100. data/try/features/encryption_fields/missing_current_key_version_try.rb +23 -0
  101. data/try/features/encryption_fields/nonce_uniqueness_try.rb +54 -0
  102. data/try/features/encryption_fields/thread_safety_try.rb +199 -0
  103. data/try/features/expiration_try.rb +0 -1
  104. data/try/features/feature_dependencies_try.rb +159 -0
  105. data/try/features/quantization_try.rb +0 -1
  106. data/try/features/real_feature_integration_try.rb +148 -0
  107. data/try/features/relatable_objects_try.rb +0 -1
  108. data/try/features/safe_dump_advanced_try.rb +0 -1
  109. data/try/features/safe_dump_try.rb +0 -1
  110. data/try/features/transient_fields/redacted_string_try.rb +248 -0
  111. data/try/features/transient_fields/refresh_reset_try.rb +164 -0
  112. data/try/features/transient_fields/simple_refresh_test.rb +50 -0
  113. data/try/features/transient_fields/single_use_redacted_string_try.rb +310 -0
  114. data/try/features/transient_fields_core_try.rb +181 -0
  115. data/try/features/transient_fields_integration_try.rb +260 -0
  116. data/try/helpers/test_helpers.rb +42 -0
  117. data/try/horreum/base_try.rb +157 -3
  118. data/try/horreum/class_methods_try.rb +27 -36
  119. data/try/horreum/enhanced_conflict_handling_try.rb +176 -0
  120. data/try/horreum/field_categories_try.rb +118 -0
  121. data/try/horreum/field_definition_try.rb +96 -0
  122. data/try/horreum/initialization_try.rb +0 -1
  123. data/try/horreum/relations_try.rb +0 -1
  124. data/try/horreum/serialization_persistent_fields_try.rb +165 -0
  125. data/try/horreum/serialization_try.rb +2 -3
  126. data/try/memory/memory_basic_test.rb +73 -0
  127. data/try/memory/memory_detailed_test.rb +121 -0
  128. data/try/memory/memory_docker_ruby_dump.sh +80 -0
  129. data/try/memory/memory_search_for_string.rb +83 -0
  130. data/try/memory/test_actual_redactedstring_protection.rb +38 -0
  131. data/try/models/customer_safe_dump_try.rb +0 -1
  132. data/try/models/customer_try.rb +0 -1
  133. data/try/models/datatype_base_try.rb +1 -2
  134. data/try/models/familia_object_try.rb +0 -1
  135. metadata +85 -18
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,270 @@
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
+ ### encrypt
59
+
60
+ Encrypts plaintext with context-specific key.
61
+
62
+ ```ruby
63
+ Familia::Encryption.encrypt(plaintext,
64
+ context: "User:favorite_snack:user123",
65
+ additional_data: nil
66
+ )
67
+ ```
68
+
69
+ **Parameters:**
70
+ - `plaintext` (String) - Data to encrypt
71
+ - `context` (String) - Key derivation context
72
+ - `additional_data` (String, nil) - Optional AAD for authentication
73
+
74
+ **Returns:** JSON string with encrypted data structure
75
+
76
+ ### decrypt
77
+
78
+ Decrypts ciphertext with context-specific key.
79
+
80
+ ```ruby
81
+ Familia::Encryption.decrypt(encrypted_json,
82
+ context: "User:favorite_snack:user123",
83
+ additional_data: nil
84
+ )
85
+ ```
86
+
87
+ **Parameters:**
88
+ - `encrypted_json` (String) - JSON-encoded encrypted data
89
+ - `context` (String) - Key derivation context
90
+ - `additional_data` (String, nil) - Optional AAD for verification
91
+
92
+ **Returns:** Decrypted plaintext string
93
+
94
+ ### validate_configuration!
95
+
96
+ Validates encryption configuration at startup.
97
+
98
+ ```ruby
99
+ Familia::Encryption.validate_configuration!
100
+ # Raises Familia::EncryptionError if configuration invalid
101
+ ```
102
+
103
+ ### with_key_cache
104
+
105
+ Provides request-scoped key caching.
106
+
107
+ ```ruby
108
+ Familia::Encryption.with_key_cache do
109
+ # Operations here share derived keys
110
+ users.each { |u| u.decrypt_fields }
111
+ end
112
+ ```
113
+
114
+ ## Configuration
115
+
116
+ ### Familia.configure
117
+
118
+ ```ruby
119
+ Familia.configure do |config|
120
+ # Single key configuration
121
+ config.encryption_keys = {
122
+ v1: ENV['FAMILIA_ENCRYPTION_KEY']
123
+ }
124
+ config.current_key_version = :v1
125
+
126
+ # Multi-version configuration
127
+ config.encryption_keys = {
128
+ v1_2024: ENV['OLD_KEY'],
129
+ v2_2025: ENV['NEW_KEY']
130
+ }
131
+ config.current_key_version = :v2_2025
132
+
133
+ # Key cache TTL (seconds)
134
+ config.key_cache_ttl = 300 # Default: 5 minutes
135
+ end
136
+ ```
137
+
138
+ ## Data Types
139
+
140
+ ### EncryptedData
141
+
142
+ Internal data structure for encrypted values.
143
+
144
+ ```ruby
145
+ EncryptedData = Data.define(
146
+ :library, # "libsodium" or "openssl"
147
+ :algorithm, # "xchacha20poly1305" or "aes-256-gcm"
148
+ :nonce, # Base64-encoded nonce/IV
149
+ :ciphertext, # Base64-encoded ciphertext
150
+ :key_version # Key version identifier
151
+ )
152
+ ```
153
+
154
+ ### RedactedString
155
+
156
+ String subclass that redacts sensitive data in output.
157
+
158
+ ```ruby
159
+ class RedactedString < String
160
+ def to_s
161
+ '[REDACTED]'
162
+ end
163
+
164
+ def inspect
165
+ '[REDACTED]'
166
+ end
167
+ end
168
+ ```
169
+
170
+ ## Exceptions
171
+
172
+ ### Familia::EncryptionError
173
+
174
+ Raised for encryption/decryption failures.
175
+
176
+ ```ruby
177
+ begin
178
+ user.decrypt_field(:favorite_snack)
179
+ rescue Familia::EncryptionError => e
180
+ case e.message
181
+ when /key version/
182
+ # Handle key version mismatch
183
+ when /authentication/
184
+ # Handle tampering
185
+ else
186
+ # Handle other errors
187
+ end
188
+ end
189
+ ```
190
+
191
+ ## CLI Commands
192
+
193
+ ### Generate Key
194
+
195
+ ```bash
196
+ $ familia encryption:generate_key [--bits 256]
197
+ # Outputs Base64-encoded key
198
+ ```
199
+
200
+ ### Verify Encryption
201
+
202
+ ```bash
203
+ $ familia encryption:verify [--model User] [--field favorite_snack]
204
+ # Verifies field encryption is working
205
+ ```
206
+
207
+ ### Rotate Keys
208
+
209
+ ```bash
210
+ $ familia encryption:rotate [--from v1] [--to v2]
211
+ # Migrates encrypted fields to new key
212
+ ```
213
+
214
+ ## Testing Helpers
215
+
216
+ ### EncryptionTestHelpers
217
+
218
+ ```ruby
219
+ module Familia::EncryptionTestHelpers
220
+ # Set up test encryption keys
221
+ def with_test_encryption_keys(&block)
222
+
223
+ # Verify field is encrypted in storage
224
+ def assert_field_encrypted(model, field)
225
+
226
+ # Verify decryption works
227
+ def assert_decryption_works(model, field, expected)
228
+ end
229
+ ```
230
+
231
+ ### RSpec Example
232
+
233
+ ```ruby
234
+ RSpec.describe User do
235
+ include Familia::EncryptionTestHelpers
236
+
237
+ it "encrypts favorite snack field" do
238
+ with_test_encryption_keys do
239
+ user = User.create(favorite_snack: "chocolate chip cookies")
240
+
241
+ assert_field_encrypted(user, :favorite_snack)
242
+ assert_decryption_works(user, :favorite_snack, "chocolate chip cookies")
243
+ end
244
+ end
245
+ end
246
+ ```
247
+
248
+ ## Performance Considerations
249
+
250
+ ### Key Derivation Caching
251
+
252
+ ```ruby
253
+ # Automatic in web requests
254
+ class ApplicationController
255
+ around_action :with_encryption_cache
256
+
257
+ def with_encryption_cache
258
+ Familia::Encryption.with_key_cache { yield }
259
+ end
260
+ end
261
+ ```
262
+
263
+ ### Batch Operations
264
+
265
+ ```ruby
266
+ # Efficient for bulk operations
267
+ User.batch_decrypt(:favorite_snack) do |users|
268
+ users.each { |u| process(u.favorite_snack) }
269
+ end
270
+ ```
@@ -0,0 +1,64 @@
1
+ # Encrypted Fields Overview
2
+
3
+ ## Quick Start
4
+
5
+ Add encrypted field support to any Familia model in one line:
6
+ class User < Familia::Horreum
7
+ encrypted_field :diary_entry
8
+ end
9
+ ```
10
+
11
+ ## What It Does
12
+
13
+ - **Automatic Encryption**: Fields are encrypted before storing in Redis/Valkey
14
+ - **Transparent Decryption**: Access encrypted fields like normal attributes
15
+ - **Secure by Default**: Uses authenticated encryption (AES-GCM or XChaCha20-Poly1305)
16
+ - **Zero Boilerplate**: No manual encrypt/decrypt calls needed
17
+
18
+ ## When to Use
19
+
20
+ Use encrypted fields for:
21
+ - Personal Identifiable Information (PII)
22
+ - API keys and secrets
23
+ - Medical records
24
+ - Financial data
25
+ - Any sensitive user data
26
+
27
+ ## Basic Example
28
+
29
+ ```ruby
30
+ class Customer < Familia::Horreum
31
+ field :email # Regular field
32
+ encrypted_field :secret_recipe # Encrypted field
33
+ encrypted_field :diary_entry # Another encrypted field
34
+ end
35
+
36
+ # Usage is identical to regular fields
37
+ customer = Customer.new(
38
+ email: 'user@example.com',
39
+ secret_recipe: 'Add extra vanilla',
40
+ diary_entry: 'Today I learned Redis is fast'
41
+ )
42
+
43
+ customer.save
44
+ customer.credit_card # => "4111-1111-1111-1111" (decrypted automatically)
45
+ ```
46
+
47
+ ## Configuration
48
+
49
+ Set your encryption key in environment:
50
+
51
+ ```bash
52
+ export FAMILIA_ENCRYPTION_KEY=$(familia encryption:generate_key)
53
+ ```
54
+
55
+ Configure in your app:
56
+
57
+ ```ruby
58
+ Familia.configure do |config|
59
+ config.encryption_keys = {
60
+ v1: ENV['FAMILIA_ENCRYPTION_KEY']
61
+ }
62
+ config.current_key_version = :v1
63
+ end
64
+ ```