familia 2.0.0.pre15 → 2.0.0.pre17

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 (288) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/.github/workflows/code-quality.yml +138 -0
  4. data/.github/workflows/code-smells.yml +85 -0
  5. data/.github/workflows/docs.yml +31 -8
  6. data/.gitignore +3 -1
  7. data/.pre-commit-config.yaml +7 -1
  8. data/.reek.yml +98 -0
  9. data/.rubocop.yml +54 -10
  10. data/.talismanrc +9 -0
  11. data/.yardopts +18 -13
  12. data/CHANGELOG.rst +86 -4
  13. data/CLAUDE.md +39 -1
  14. data/Gemfile +6 -5
  15. data/Gemfile.lock +99 -23
  16. data/LICENSE.txt +1 -1
  17. data/README.md +285 -85
  18. data/changelog.d/README.md +2 -2
  19. data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
  20. data/docs/archive/FAMILIA_TECHNICAL.md +42 -42
  21. data/docs/archive/FAMILIA_UPDATE.md +3 -3
  22. data/docs/archive/README.md +3 -2
  23. data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
  24. data/docs/conf.py +29 -0
  25. data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
  26. data/docs/guides/feature-encrypted-fields.md +785 -0
  27. data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
  28. data/docs/guides/feature-external-identifiers.md +637 -0
  29. data/docs/guides/feature-object-identifiers.md +435 -0
  30. data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
  31. data/docs/guides/feature-relationships-methods.md +684 -0
  32. data/docs/guides/feature-relationships.md +200 -0
  33. data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
  34. data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
  35. data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
  36. data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
  37. data/docs/guides/index.md +176 -0
  38. data/docs/guides/{Security-Model.md → security-model.md} +1 -1
  39. data/docs/migrating/v2.0.0-pre.md +1 -1
  40. data/docs/migrating/v2.0.0-pre11.md +2 -2
  41. data/docs/migrating/v2.0.0-pre12.md +2 -2
  42. data/docs/migrating/v2.0.0-pre5.md +33 -12
  43. data/docs/migrating/v2.0.0-pre6.md +2 -2
  44. data/docs/migrating/v2.0.0-pre7.md +8 -8
  45. data/docs/overview.md +624 -20
  46. data/docs/reference/api-technical.md +1365 -0
  47. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
  48. data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
  49. data/examples/autoloader/mega_customer.rb +3 -1
  50. data/examples/encrypted_fields.rb +378 -0
  51. data/examples/json_usage_patterns.rb +144 -0
  52. data/examples/relationships.rb +13 -13
  53. data/examples/safe_dump.rb +7 -7
  54. data/examples/single_connection_transaction_confusions.rb +379 -0
  55. data/lib/familia/base.rb +51 -10
  56. data/lib/familia/connection/handlers.rb +223 -0
  57. data/lib/familia/connection/individual_command_proxy.rb +64 -0
  58. data/lib/familia/connection/middleware.rb +75 -0
  59. data/lib/familia/connection/operation_core.rb +93 -0
  60. data/lib/familia/connection/operations.rb +277 -0
  61. data/lib/familia/connection/pipeline_core.rb +87 -0
  62. data/lib/familia/connection/transaction_core.rb +100 -0
  63. data/lib/familia/connection.rb +60 -186
  64. data/lib/familia/data_type/class_methods.rb +63 -0
  65. data/lib/familia/data_type/commands.rb +53 -51
  66. data/lib/familia/data_type/connection.rb +83 -0
  67. data/lib/familia/data_type/serialization.rb +108 -107
  68. data/lib/familia/data_type/settings.rb +96 -0
  69. data/lib/familia/data_type/types/counter.rb +1 -1
  70. data/lib/familia/data_type/types/hashkey.rb +15 -11
  71. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  72. data/lib/familia/data_type/types/lock.rb +3 -2
  73. data/lib/familia/data_type/types/sorted_set.rb +128 -14
  74. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -9
  75. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  76. data/lib/familia/data_type.rb +12 -171
  77. data/lib/familia/distinguisher.rb +85 -0
  78. data/lib/familia/encryption/encrypted_data.rb +15 -24
  79. data/lib/familia/encryption/manager.rb +6 -4
  80. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  81. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  82. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  83. data/lib/familia/encryption/request_cache.rb +7 -7
  84. data/lib/familia/encryption.rb +2 -3
  85. data/lib/familia/errors.rb +9 -3
  86. data/lib/familia/features/autoloader.rb +30 -12
  87. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  88. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  89. data/lib/familia/features/encrypted_fields.rb +71 -66
  90. data/lib/familia/features/expiration/extensions.rb +1 -1
  91. data/lib/familia/features/expiration.rb +31 -26
  92. data/lib/familia/features/external_identifier.rb +57 -19
  93. data/lib/familia/features/object_identifier.rb +134 -25
  94. data/lib/familia/features/quantization.rb +16 -21
  95. data/lib/familia/features/relationships/README.md +97 -0
  96. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  97. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  98. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +306 -0
  99. data/lib/familia/features/relationships/indexing.rb +182 -256
  100. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  101. data/lib/familia/features/relationships/participation/participant_methods.rb +164 -0
  102. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  103. data/lib/familia/features/relationships/participation.rb +656 -0
  104. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  105. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  106. data/lib/familia/features/relationships.rb +65 -266
  107. data/lib/familia/features/safe_dump.rb +127 -130
  108. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  109. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  110. data/lib/familia/features/transient_fields.rb +10 -7
  111. data/lib/familia/features.rb +10 -14
  112. data/lib/familia/field_type.rb +6 -4
  113. data/lib/familia/horreum/connection.rb +297 -0
  114. data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +27 -17
  115. data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +139 -74
  116. data/lib/familia/horreum/{subclass/management.rb → management.rb} +73 -27
  117. data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +108 -185
  118. data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +104 -23
  119. data/lib/familia/horreum/serialization.rb +172 -0
  120. data/lib/familia/horreum/{shared/settings.rb → settings.rb} +2 -1
  121. data/lib/familia/horreum/{core/utils.rb → utils.rb} +2 -1
  122. data/lib/familia/horreum.rb +222 -119
  123. data/lib/familia/json_serializer.rb +0 -1
  124. data/lib/familia/logging.rb +11 -114
  125. data/lib/familia/refinements/dear_json.rb +122 -0
  126. data/lib/familia/refinements/logger_trace.rb +20 -17
  127. data/lib/familia/refinements/stylize_words.rb +65 -0
  128. data/lib/familia/refinements/time_literals.rb +60 -52
  129. data/lib/familia/refinements.rb +2 -1
  130. data/lib/familia/secure_identifier.rb +60 -28
  131. data/lib/familia/settings.rb +83 -7
  132. data/lib/familia/utils.rb +5 -87
  133. data/lib/familia/verifiable_identifier.rb +4 -4
  134. data/lib/familia/version.rb +1 -1
  135. data/lib/familia.rb +72 -14
  136. data/lib/middleware/database_middleware.rb +56 -14
  137. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  138. data/try/configuration/scenarios_try.rb +2 -2
  139. data/try/connection/fiber_context_preservation_try.rb +250 -0
  140. data/try/connection/handler_constraints_try.rb +59 -0
  141. data/try/connection/operation_mode_guards_try.rb +208 -0
  142. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  143. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  144. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  145. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  146. data/try/connection/transaction_mode_strict_try.rb +98 -0
  147. data/try/connection/transaction_mode_warn_try.rb +131 -0
  148. data/try/connection/transaction_modes_try.rb +249 -0
  149. data/try/core/autoloader_try.rb +120 -2
  150. data/try/core/connection_try.rb +10 -10
  151. data/try/core/conventional_inheritance_try.rb +130 -0
  152. data/try/core/create_method_try.rb +15 -23
  153. data/try/core/database_consistency_try.rb +11 -10
  154. data/try/core/errors_try.rb +11 -14
  155. data/try/core/familia_extended_try.rb +2 -2
  156. data/try/core/familia_members_methods_try.rb +76 -0
  157. data/try/core/familia_try.rb +1 -1
  158. data/try/core/isolated_dbclient_try.rb +165 -0
  159. data/try/core/middleware_try.rb +16 -16
  160. data/try/core/persistence_operations_try.rb +4 -4
  161. data/try/core/pools_try.rb +42 -26
  162. data/try/core/secure_identifier_try.rb +28 -24
  163. data/try/core/time_utils_try.rb +10 -10
  164. data/try/core/tools_try.rb +3 -3
  165. data/try/core/utils_try.rb +2 -2
  166. data/try/data_types/boolean_try.rb +4 -4
  167. data/try/data_types/datatype_base_try.rb +0 -2
  168. data/try/data_types/list_try.rb +10 -10
  169. data/try/data_types/sorted_set_try.rb +5 -5
  170. data/try/data_types/sorted_set_zadd_options_try.rb +625 -0
  171. data/try/data_types/string_try.rb +12 -12
  172. data/try/data_types/unsortedset_try.rb +33 -0
  173. data/try/debugging/cache_behavior_tracer.rb +7 -7
  174. data/try/debugging/debug_aad_process.rb +1 -1
  175. data/try/debugging/debug_concealed_internal.rb +1 -1
  176. data/try/debugging/debug_cross_context.rb +1 -1
  177. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  178. data/try/debugging/encryption_method_tracer.rb +10 -10
  179. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  180. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  181. data/try/encryption/config_persistence_try.rb +2 -2
  182. data/try/encryption/encryption_core_try.rb +19 -19
  183. data/try/encryption/instance_variable_scope_try.rb +1 -1
  184. data/try/encryption/module_loading_try.rb +2 -2
  185. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  186. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  187. data/try/encryption/secure_memory_handling_try.rb +1 -1
  188. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  189. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  190. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  191. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  192. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  193. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  194. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  195. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  196. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  197. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  198. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  199. data/try/features/feature_dependencies_try.rb +3 -3
  200. data/try/features/field_groups_try.rb +244 -0
  201. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  202. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  203. data/try/features/quantization/quantization_try.rb +1 -1
  204. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  205. data/try/features/relationships/indexing_try.rb +443 -0
  206. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  207. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  208. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  209. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  210. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  211. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  212. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  213. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  214. data/try/features/relationships/relationships_performance_try.rb +20 -20
  215. data/try/features/relationships/relationships_try.rb +27 -38
  216. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  217. data/try/features/transient_fields/refresh_reset_try.rb +3 -1
  218. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  219. data/try/helpers/test_cleanup.rb +86 -0
  220. data/try/helpers/test_helpers.rb +6 -7
  221. data/try/horreum/auto_indexing_on_save_try.rb +212 -0
  222. data/try/horreum/base_try.rb +3 -2
  223. data/try/horreum/commands_try.rb +3 -1
  224. data/try/horreum/defensive_initialization_try.rb +86 -0
  225. data/try/horreum/destroy_related_fields_cleanup_try.rb +332 -0
  226. data/try/horreum/initialization_try.rb +11 -7
  227. data/try/horreum/relations_try.rb +21 -13
  228. data/try/horreum/serialization_try.rb +12 -11
  229. data/try/horreum/settings_try.rb +2 -0
  230. data/try/integration/cross_component_try.rb +3 -3
  231. data/try/memory/memory_basic_test.rb +1 -1
  232. data/try/memory/memory_docker_ruby_dump.sh +2 -2
  233. data/try/models/customer_safe_dump_try.rb +1 -1
  234. data/try/models/customer_try.rb +13 -15
  235. data/try/models/datatype_base_try.rb +3 -3
  236. data/try/models/familia_object_try.rb +9 -8
  237. data/try/performance/benchmarks_try.rb +2 -2
  238. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  239. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  240. data/try/prototypes/atomic_saves_v4.rb +1 -1
  241. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  242. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  243. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  244. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  245. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  246. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  247. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  248. data/try/prototypes/pooling/pool_siege.rb +11 -11
  249. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  250. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  251. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  252. data/try/refinements/logger_trace_methods_try.rb +44 -0
  253. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  254. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  255. data/try/valkey.conf +26 -0
  256. metadata +92 -52
  257. data/.rubocop_todo.yml +0 -208
  258. data/docs/connection_pooling.md +0 -192
  259. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  260. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  261. data/docs/guides/Feature-System-Autoloading.md +0 -198
  262. data/docs/guides/Home.md +0 -116
  263. data/docs/guides/Relationships-Guide.md +0 -737
  264. data/docs/guides/relationships-methods.md +0 -266
  265. data/docs/reference/auditing_database_commands.rb +0 -228
  266. data/examples/permissions.rb +0 -240
  267. data/lib/familia/features/relationships/cascading.rb +0 -437
  268. data/lib/familia/features/relationships/membership.rb +0 -497
  269. data/lib/familia/features/relationships/permission_management.rb +0 -264
  270. data/lib/familia/features/relationships/querying.rb +0 -615
  271. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  272. data/lib/familia/features/relationships/tracking.rb +0 -418
  273. data/lib/familia/horreum/core/connection.rb +0 -73
  274. data/lib/familia/horreum/core.rb +0 -21
  275. data/lib/familia/refinements/snake_case.rb +0 -40
  276. data/lib/familia/validation/command_recorder.rb +0 -336
  277. data/lib/familia/validation/expectations.rb +0 -519
  278. data/lib/familia/validation/validation_helpers.rb +0 -443
  279. data/lib/familia/validation/validator.rb +0 -412
  280. data/lib/familia/validation.rb +0 -140
  281. data/try/data_types/set_try.rb +0 -33
  282. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  283. data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
  284. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
  285. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  286. data/try/validation/command_validation_try.rb.disabled +0 -207
  287. data/try/validation/performance_validation_try.rb.disabled +0 -324
  288. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -0,0 +1,785 @@
1
+ # Encrypted Fields Guide
2
+
3
+ > **💡 Quick Reference**
4
+ >
5
+ > Add persistent encrypted storage to any Familia model:
6
+ > ```ruby
7
+ > class User < Familia::Horreum
8
+ > feature :encrypted_fields
9
+ > encrypted_field :sensitive_data
10
+ > end
11
+ > ```
12
+
13
+ ## Overview
14
+
15
+ The Encrypted Fields feature provides transparent field-level encryption for sensitive data stored in Redis/Valkey. It combines industry-standard encryption algorithms with Ruby-friendly APIs, ensuring your sensitive data is protected at rest while maintaining the performance and simplicity you expect from Familia.
16
+
17
+ ## Why Use Encrypted Fields?
18
+
19
+ **Compliance**: Meet regulatory requirements (GDPR, HIPAA, PCI-DSS) for sensitive data protection.
20
+
21
+ **Defense in Depth**: Protect against database breaches, memory dumps, and unauthorized Valkey/Redis access.
22
+
23
+ **Transparent Security**: Encryption and decryption happen automatically - no changes to your application logic.
24
+
25
+ **Performance Focused**: Optimized for Ruby's memory model with request-level caching and efficient key derivation.
26
+
27
+ **Future Proof**: Modular provider system supports algorithm upgrades and key rotation.
28
+
29
+ > **⚠️ Security Consideration**
30
+ >
31
+ > Encrypted fields protect data at rest in Redis/Valkey. Consider your threat model: encryption keys are held in Ruby memory and may be visible in memory dumps.
32
+
33
+ ## Quick Start
34
+
35
+ ### Basic Encrypted Storage
36
+
37
+ ```ruby
38
+ class Customer < Familia::Horreum
39
+ feature :encrypted_fields
40
+
41
+ # Regular fields (stored as plaintext)
42
+ field :email, :company_name, :created_at
43
+
44
+ # Encrypted fields (automatically encrypted/decrypted)
45
+ encrypted_field :api_key
46
+ encrypted_field :notes
47
+ encrypted_field :credit_card_last_four
48
+ end
49
+
50
+ # Configure encryption keys
51
+ Familia.configure do |config|
52
+ config.encryption_keys = {
53
+ v1: ENV['FAMILIA_ENCRYPTION_KEY']
54
+ }
55
+ config.current_key_version = :v1
56
+ end
57
+
58
+ # Usage is identical to regular fields
59
+ customer = Customer.new(
60
+ email: 'contact@acme.com',
61
+ company_name: 'Acme Corporation',
62
+ api_key: 'sk-1234567890abcdef',
63
+ notes: 'VIP customer - handle with care'
64
+ )
65
+
66
+ customer.save
67
+
68
+ # Access returns ConcealedString for safety
69
+ customer.api_key.class # => ConcealedString
70
+ customer.api_key.to_s # => "[CONCEALED]" (safe for logging)
71
+ customer.api_key.reveal # => "sk-1234567890abcdef" (actual value)
72
+ ```
73
+
74
+ ### Key Generation and Setup
75
+
76
+ Generate secure encryption keys for your environment:
77
+
78
+ ```bash
79
+ # Generate a new 32-byte key
80
+ export FAMILIA_ENCRYPTION_KEY=$(openssl rand -base64 32)
81
+
82
+ # For production, use a secure key management service
83
+ export FAMILIA_ENCRYPTION_KEY_V1="your-secure-key-from-vault"
84
+ export FAMILIA_ENCRYPTION_KEY_V2="new-key-for-rotation"
85
+ ```
86
+
87
+ > **🔒 Key Management Best Practices**
88
+ >
89
+ > - Use different keys for each environment (development, staging, production)
90
+ > - Store keys in a secure key management service (AWS KMS, HashiCorp Vault)
91
+ > - Never commit keys to source control
92
+ > - Rotate keys regularly (recommended: every 90-180 days)
93
+
94
+ ## Configuration Deep Dive
95
+
96
+ ### Basic Configuration
97
+
98
+ ```ruby
99
+ Familia.configure do |config|
100
+ # Single key setup (simplest)
101
+ config.encryption_keys = {
102
+ v1: ENV['FAMILIA_ENCRYPTION_KEY']
103
+ }
104
+ config.current_key_version = :v1
105
+
106
+ # Optional: application-specific key derivation
107
+ config.encryption_personalization = 'MyApp-Production-2024'
108
+ end
109
+
110
+ # Validate configuration before use
111
+ Familia::Encryption.validate_configuration!
112
+ ```
113
+
114
+ ### Multi-Key Configuration (Key Rotation)
115
+
116
+ ```ruby
117
+ Familia.configure do |config|
118
+ # Multiple keys for rotation
119
+ config.encryption_keys = {
120
+ v1: ENV['FAMILIA_ENCRYPTION_KEY_V1'], # Legacy key
121
+ v2: ENV['FAMILIA_ENCRYPTION_KEY_V2'], # Current key
122
+ v3: ENV['FAMILIA_ENCRYPTION_KEY_V3'] # New key for rotation
123
+ }
124
+
125
+ # New data encrypted with v3, old data readable with v1/v2
126
+ config.current_key_version = :v3
127
+
128
+ # Provider-specific configuration
129
+ config.encryption_providers = {
130
+ xchacha20_poly1305: {
131
+ priority: 100,
132
+ require_gem: 'rbnacl'
133
+ },
134
+ aes_gcm: {
135
+ priority: 50,
136
+ always_available: true
137
+ }
138
+ }
139
+ end
140
+ ```
141
+
142
+ > **💡 Key Rotation Strategy**
143
+ >
144
+ > 1. Add new key version to configuration
145
+ > 2. Update `current_key_version` to new version
146
+ > 3. Deploy application (new writes use new key)
147
+ > 4. Re-encrypt existing data with `re_encrypt_fields!`
148
+ > 5. Remove old key version after migration complete
149
+
150
+ ## Encryption Providers
151
+
152
+ Familia supports multiple encryption algorithms with automatic provider selection:
153
+
154
+ ### XChaCha20-Poly1305 (Recommended)
155
+
156
+ The preferred encryption algorithm offering excellent security and performance:
157
+
158
+ ```ruby
159
+ # Requires rbnacl gem
160
+ gem 'rbnacl', '~> 7.1'
161
+
162
+ # Automatic selection when available
163
+ class SecureVault < Familia::Horreum
164
+ feature :encrypted_fields
165
+
166
+ # Will use XChaCha20-Poly1305 if rbnacl is available
167
+ encrypted_field :master_password
168
+ encrypted_field :recovery_codes
169
+ end
170
+ ```
171
+
172
+ **Characteristics:**
173
+ - **Algorithm**: XChaCha20-Poly1305 AEAD
174
+ - **Key Size**: 32 bytes (256 bits)
175
+ - **Nonce Size**: 24 bytes (192 bits) - collision resistant
176
+ - **Authentication**: Built-in with Poly1305 MAC
177
+ - **Performance**: Excellent on modern CPUs
178
+
179
+ > **🚀 Performance Tip**
180
+ >
181
+ > XChaCha20-Poly1305 is typically 20-30% faster than AES-GCM and provides better security margins.
182
+
183
+ ### AES-256-GCM (Fallback)
184
+
185
+ Standard AES encryption using OpenSSL (always available):
186
+
187
+ ```ruby
188
+ # No additional gems required
189
+ class StandardVault < Familia::Horreum
190
+ feature :encrypted_fields
191
+
192
+ # Explicitly specify AES-GCM
193
+ encrypted_field :secret_data, provider: :aes_gcm
194
+ end
195
+ ```
196
+
197
+ **Characteristics:**
198
+ - **Algorithm**: AES-256-GCM AEAD
199
+ - **Key Size**: 32 bytes (256 bits)
200
+ - **IV Size**: 12 bytes (96 bits)
201
+ - **Authentication**: Built-in with GCM mode
202
+ - **Availability**: Always available via OpenSSL
203
+
204
+ ### Provider Selection Logic
205
+
206
+ ```ruby
207
+ # Check available providers
208
+ providers = Familia::Encryption.available_providers
209
+ # => [
210
+ # { name: :xchacha20_poly1305, priority: 100, available: true },
211
+ # { name: :aes_gcm, priority: 50, available: true }
212
+ # ]
213
+
214
+ # Force specific provider for testing
215
+ class TestVault < Familia::Horreum
216
+ feature :encrypted_fields
217
+
218
+ encrypted_field :test_data, provider: :aes_gcm # Force AES-GCM
219
+ end
220
+ ```
221
+
222
+ ## Advanced Field Configuration
223
+
224
+ ### Additional Authenticated Data (AAD)
225
+
226
+ Protect against field tampering by including related fields in authentication:
227
+
228
+ ```ruby
229
+ class SecureDocument < Familia::Horreum
230
+ feature :encrypted_fields
231
+
232
+ field :document_id, :owner_id, :created_at
233
+
234
+ # Include document_id and owner_id in authentication
235
+ encrypted_field :content, aad_fields: [:document_id, :owner_id]
236
+ encrypted_field :metadata, aad_fields: [:document_id, :created_at]
237
+ end
238
+
239
+ # AAD fields are included in encryption but not encrypted themselves
240
+ doc = SecureDocument.new(
241
+ document_id: 'doc123',
242
+ owner_id: 'user456',
243
+ content: 'Sensitive document content',
244
+ created_at: Time.now.to_i
245
+ )
246
+
247
+ doc.save
248
+
249
+ # If someone modifies document_id or owner_id, decryption will fail
250
+ # This prevents attacks where encrypted data is moved between records
251
+ ```
252
+
253
+ > **🛡️ Security Enhancement**
254
+ >
255
+ > AAD prevents encrypted fields from being moved between objects. Use it for high-security scenarios where data integrity is critical.
256
+
257
+ ### Per-Field Provider Selection
258
+
259
+ ```ruby
260
+ class MultiAlgorithmVault < Familia::Horreum
261
+ feature :encrypted_fields
262
+
263
+ # Use best available algorithm (XChaCha20-Poly1305 preferred)
264
+ encrypted_field :general_secret
265
+
266
+ # Force AES-GCM for compliance requirements
267
+ encrypted_field :compliance_data, provider: :aes_gcm
268
+
269
+ # High-security field with AAD
270
+ encrypted_field :ultra_secure,
271
+ provider: :xchacha20_poly1305,
272
+ aad_fields: [:vault_id, :owner_id]
273
+ end
274
+ ```
275
+
276
+ ## ConcealedString Security
277
+
278
+ Encrypted fields return `ConcealedString` objects to prevent accidental exposure:
279
+
280
+ ### Safe Handling
281
+
282
+ ```ruby
283
+ class User < Familia::Horreum
284
+ feature :encrypted_fields
285
+ encrypted_field :api_key
286
+ end
287
+
288
+ user = User.create(api_key: "sk-1234567890abcdef")
289
+
290
+ # Safe operations (won't expose actual value)
291
+ puts user.api_key.to_s # => "[CONCEALED]"
292
+ puts user.api_key.inspect # => "[CONCEALED]"
293
+ logger.info("User key: #{user.api_key}") # => "User key: [CONCEALED]"
294
+
295
+ # JSON serialization safety
296
+ user_json = user.to_json
297
+ # All encrypted fields appear as "[CONCEALED]" in JSON
298
+
299
+ # Explicit access when needed
300
+ actual_key = user.api_key.reveal # => "sk-1234567890abcdef"
301
+ ```
302
+
303
+ ### String Operations
304
+
305
+ ```ruby
306
+ api_key = user.api_key
307
+
308
+ # Length operations work on concealed representation
309
+ api_key.length # => 11 ("[CONCEALED]".length)
310
+ api_key.size # => 11
311
+
312
+ # Comparison operations
313
+ api_key == "[CONCEALED]" # => true
314
+ api_key.start_with?("[CONCEALED]") # => true
315
+
316
+ # Reveal for actual operations
317
+ actual_key = api_key.reveal
318
+ actual_key.length # => 17 (actual key length)
319
+ actual_key.start_with?("sk-") # => true
320
+ ```
321
+
322
+ > **⚠️ Important**
323
+ >
324
+ > Always use `.reveal` explicitly when you need the actual value. This makes it obvious in code reviews where sensitive data is being accessed.
325
+
326
+ ## Performance Optimization
327
+
328
+ ### Request-Level Caching
329
+
330
+ For applications that perform many encryption operations:
331
+
332
+ ```ruby
333
+ class BulkDataProcessor
334
+ def process_sensitive_batch(records)
335
+ # Enable key caching for the entire batch
336
+ Familia::Encryption.with_request_cache do
337
+ records.each do |record|
338
+ # Key derivation happens once per field type
339
+ record.encrypted_field1 = process_data(record.raw_data1)
340
+ record.encrypted_field2 = process_data(record.raw_data2)
341
+ record.save
342
+ end
343
+ end
344
+ # Cache automatically cleared at end of block
345
+ end
346
+ end
347
+ ```
348
+
349
+ **Performance Improvements:**
350
+ - Key derivation: 1x per field type instead of per operation
351
+ - Typical improvement: 40-60% faster for batch operations
352
+ - Memory usage: Minimal (keys cached temporarily)
353
+
354
+ ### Benchmarking Your Setup
355
+
356
+ ```ruby
357
+ # Test encryption performance with your data
358
+ def benchmark_encryption
359
+ require 'benchmark'
360
+
361
+ test_data = {
362
+ small: "x" * 100,
363
+ medium: "x" * 1000,
364
+ large: "x" * 10000
365
+ }
366
+
367
+ Benchmark.bm(15) do |x|
368
+ test_data.each do |size, data|
369
+ x.report("#{size} (#{data.length}b)") do
370
+ 1000.times do
371
+ TestModel.create(encrypted_field: data)
372
+ end
373
+ end
374
+ end
375
+ end
376
+ end
377
+
378
+ # Provider comparison
379
+ providers = Familia::Encryption.benchmark_providers(iterations: 1000)
380
+ providers.each do |name, stats|
381
+ puts "#{name}: #{stats[:ops_per_sec]} ops/sec"
382
+ end
383
+ ```
384
+
385
+ ### Memory Management
386
+
387
+ ```ruby
388
+ class MemoryAwareModel < Familia::Horreum
389
+ feature :encrypted_fields
390
+ encrypted_field :large_data
391
+
392
+ def process_and_clear
393
+ # Process encrypted data
394
+ result = expensive_operation(large_data.reveal)
395
+
396
+ # Clear sensitive data from memory
397
+ clear_encrypted_fields!
398
+
399
+ result
400
+ end
401
+
402
+ def self.bulk_process_with_cleanup(ids)
403
+ ids.each_slice(100) do |batch|
404
+ objects = multiget(batch)
405
+
406
+ objects.each(&:process_and_clear)
407
+
408
+ # Force garbage collection periodically
409
+ GC.start if batch.first % 1000 == 0
410
+ end
411
+ end
412
+ end
413
+ ```
414
+
415
+ ## Key Rotation and Migration
416
+
417
+ ### Planned Key Rotation
418
+
419
+ ```ruby
420
+ # 1. Add new key to configuration
421
+ Familia.configure do |config|
422
+ config.encryption_keys = {
423
+ v1: ENV['OLD_KEY'],
424
+ v2: ENV['CURRENT_KEY'],
425
+ v3: ENV['NEW_KEY'] # Add new key
426
+ }
427
+ config.current_key_version = :v3 # Switch to new key
428
+ end
429
+
430
+ # 2. Deploy application (new data uses v3)
431
+
432
+ # 3. Migrate existing data
433
+ class KeyRotationTask
434
+ def self.rotate_all_encrypted_data
435
+ model_classes = [User, Document, Vault, SecretData]
436
+
437
+ model_classes.each do |model_class|
438
+ puts "Rotating keys for #{model_class.name}..."
439
+
440
+ model_class.all.each_slice(100) do |batch|
441
+ batch.each do |record|
442
+ begin
443
+ record.re_encrypt_fields!
444
+ record.save
445
+ rescue => e
446
+ puts "Failed to rotate #{record.identifier}: #{e.message}"
447
+ end
448
+ end
449
+
450
+ print "."
451
+ sleep 0.1 # Rate limiting
452
+ end
453
+
454
+ puts "\nCompleted #{model_class.name}"
455
+ end
456
+ end
457
+ end
458
+
459
+ # 4. Remove old key after migration
460
+ ```
461
+
462
+ ### Emergency Key Rotation
463
+
464
+ ```ruby
465
+ # For compromised keys, rotate immediately
466
+ class EmergencyRotation
467
+ def self.emergency_key_rotation
468
+ # 1. Generate new key immediately
469
+ new_key = SecureRandom.base64(32)
470
+
471
+ # 2. Update configuration
472
+ Familia.configure do |config|
473
+ config.encryption_keys[:emergency] = new_key
474
+ config.current_key_version = :emergency
475
+ end
476
+
477
+ # 3. Re-encrypt all data immediately
478
+ KeyRotationTask.rotate_all_encrypted_data
479
+
480
+ # 4. Notify security team
481
+ SecurityNotifier.alert_key_rotated(reason: 'emergency')
482
+ end
483
+ end
484
+ ```
485
+
486
+ ## Error Handling and Debugging
487
+
488
+ ### Common Configuration Errors
489
+
490
+ ```ruby
491
+ begin
492
+ Familia::Encryption.validate_configuration!
493
+ rescue Familia::EncryptionError => e
494
+ case e.message
495
+ when /No encryption keys configured/
496
+ puts "Add encryption keys to Familia.configure block"
497
+ when /Invalid key format/
498
+ puts "Keys must be base64-encoded 32-byte strings"
499
+ when /Current key version not found/
500
+ puts "current_key_version must exist in encryption_keys"
501
+ else
502
+ puts "Configuration error: #{e.message}"
503
+ end
504
+ end
505
+ ```
506
+
507
+ ### Debugging Encryption Issues
508
+
509
+ ```ruby
510
+ class DebugVault < Familia::Horreum
511
+ feature :encrypted_fields
512
+ encrypted_field :debug_data
513
+
514
+ def debug_encryption_status
515
+ status = {
516
+ feature_enabled: self.class.features_enabled.include?(:encrypted_fields),
517
+ field_encrypted: self.class.encrypted_field?(:debug_data),
518
+ data_encrypted: encrypted_data?,
519
+ fields_cleared: encrypted_fields_cleared?,
520
+ current_provider: Familia::Encryption.current_provider,
521
+ available_providers: Familia::Encryption.available_providers
522
+ }
523
+
524
+ puts JSON.pretty_generate(status)
525
+ status
526
+ end
527
+ end
528
+
529
+ # Debug individual field encryption
530
+ vault = DebugVault.new(debug_data: "test")
531
+ vault.save
532
+
533
+ field_status = vault.encrypted_fields_status
534
+ puts "Field status: #{field_status}"
535
+ # => {debug_data: {encrypted: true, key_version: :v2, provider: :xchacha20_poly1305}}
536
+ ```
537
+
538
+ ### Performance Debugging
539
+
540
+ ```ruby
541
+ # Monitor encryption performance
542
+ class EncryptionMonitor
543
+ def self.monitor_encryption_calls
544
+ original_encrypt = Familia::Encryption.method(:encrypt)
545
+
546
+ call_count = 0
547
+ total_time = 0
548
+
549
+ Familia::Encryption.define_singleton_method(:encrypt) do |data, **opts|
550
+ start_time = Time.now
551
+ result = original_encrypt.call(data, **opts)
552
+ total_time += (Time.now - start_time)
553
+ call_count += 1
554
+
555
+ if call_count % 100 == 0
556
+ avg_time = (total_time / call_count * 1000).round(2)
557
+ puts "Encryption calls: #{call_count}, avg: #{avg_time}ms"
558
+ end
559
+
560
+ result
561
+ end
562
+ end
563
+ end
564
+ ```
565
+
566
+ ## Testing Strategies
567
+
568
+ ### Test Configuration
569
+
570
+ ```ruby
571
+ # test/test_helper.rb
572
+ require 'familia'
573
+
574
+ # Use predictable test keys
575
+ test_keys = {
576
+ v1: Base64.strict_encode64('a' * 32),
577
+ v2: Base64.strict_encode64('b' * 32)
578
+ }
579
+
580
+ Familia.configure do |config|
581
+ config.encryption_keys = test_keys
582
+ config.current_key_version = :v1
583
+ config.encryption_personalization = 'TestApp-Test'
584
+ end
585
+
586
+ # Validate test configuration
587
+ Familia::Encryption.validate_configuration!
588
+ ```
589
+
590
+ ### Testing Encrypted Fields
591
+
592
+ ```ruby
593
+ # test/models/encrypted_model_test.rb
594
+ require 'test_helper'
595
+
596
+ class EncryptedModelTest < Minitest::Test
597
+ def setup
598
+ @model = EncryptedModel.new(
599
+ name: "Test Model",
600
+ secret_data: "sensitive information"
601
+ )
602
+ @model.save
603
+ end
604
+
605
+ def test_encryption_concealment
606
+ # Field should return ConcealedString
607
+ assert_instance_of Familia::Features::EncryptedFields::ConcealedString, @model.secret_data
608
+
609
+ # String representation should be concealed
610
+ assert_equal "[CONCEALED]", @model.secret_data.to_s
611
+
612
+ # Reveal should return actual value
613
+ assert_equal "sensitive information", @model.secret_data.reveal
614
+ end
615
+
616
+ def test_json_serialization_safety
617
+ json_data = @model.to_json
618
+ parsed = JSON.parse(json_data)
619
+
620
+ # Encrypted fields should be concealed in JSON
621
+ assert_equal "[CONCEALED]", parsed['secret_data']
622
+
623
+ # Regular fields should be normal
624
+ assert_equal "Test Model", parsed['name']
625
+ end
626
+
627
+ def test_encryption_persistence
628
+ # Reload from database
629
+ reloaded = EncryptedModel.load(@model.identifier)
630
+
631
+ # Should still be able to decrypt
632
+ assert_equal "sensitive information", reloaded.secret_data.reveal
633
+ end
634
+
635
+ def test_key_rotation
636
+ original_data = @model.secret_data.reveal
637
+
638
+ # Simulate key rotation
639
+ @model.re_encrypt_fields!
640
+ @model.save
641
+
642
+ # Should still decrypt to same value
643
+ reloaded = EncryptedModel.load(@model.identifier)
644
+ assert_equal original_data, reloaded.secret_data.reveal
645
+ end
646
+ end
647
+ ```
648
+
649
+ ### Mock Encryption for Fast Tests
650
+
651
+ ```ruby
652
+ # test/support/mock_encryption.rb
653
+ module MockEncryption
654
+ def self.setup
655
+ # Replace encryption with reversible encoding for speed
656
+ Familia::Encryption.define_singleton_method(:encrypt) do |data, **opts|
657
+ Base64.strict_encode64("MOCK:#{data}")
658
+ end
659
+
660
+ Familia::Encryption.define_singleton_method(:decrypt) do |encrypted_data, **opts|
661
+ decoded = Base64.strict_decode64(encrypted_data)
662
+ decoded.sub(/^MOCK:/, '')
663
+ end
664
+ end
665
+
666
+ def self.teardown
667
+ # Restore original encryption methods
668
+ load 'familia/encryption.rb'
669
+ end
670
+ end
671
+
672
+ # Use in fast test suite
673
+ class FastEncryptedModelTest < Minitest::Test
674
+ def setup
675
+ MockEncryption.setup
676
+ end
677
+
678
+ def teardown
679
+ MockEncryption.teardown
680
+ end
681
+
682
+ # Tests run much faster with mock encryption
683
+ end
684
+ ```
685
+
686
+ ## Production Considerations
687
+
688
+ ### Monitoring and Alerting
689
+
690
+ ```ruby
691
+ # Monitor encryption health in production
692
+ class EncryptionHealthCheck
693
+ def self.check
694
+ results = {
695
+ configuration_valid: false,
696
+ providers_available: [],
697
+ key_versions_accessible: [],
698
+ sample_encrypt_decrypt: false
699
+ }
700
+
701
+ begin
702
+ # Test configuration
703
+ Familia::Encryption.validate_configuration!
704
+ results[:configuration_valid] = true
705
+
706
+ # Test providers
707
+ results[:providers_available] = Familia::Encryption.available_providers.map { |p| p[:name] }
708
+
709
+ # Test key access
710
+ Familia.config.encryption_keys.each do |version, key|
711
+ begin
712
+ # Test key derivation
713
+ Familia::Encryption.derive_key_for_field('test_field', version)
714
+ results[:key_versions_accessible] << version
715
+ rescue => e
716
+ puts "Key version #{version} error: #{e.message}"
717
+ end
718
+ end
719
+
720
+ # Test encrypt/decrypt cycle
721
+ test_data = "health_check_#{Time.now.to_i}"
722
+ encrypted = Familia::Encryption.encrypt(test_data)
723
+ decrypted = Familia::Encryption.decrypt(encrypted)
724
+ results[:sample_encrypt_decrypt] = (decrypted == test_data)
725
+
726
+ rescue => e
727
+ results[:error] = e.message
728
+ end
729
+
730
+ results
731
+ end
732
+ end
733
+
734
+ # Set up monitoring
735
+ # Nagios, DataDog, or other monitoring
736
+ results = EncryptionHealthCheck.check
737
+ if results[:configuration_valid] && results[:sample_encrypt_decrypt]
738
+ exit 0 # OK
739
+ else
740
+ puts "Encryption health check failed: #{results}"
741
+ exit 2 # Critical
742
+ end
743
+ ```
744
+
745
+ ### Backup and Recovery
746
+
747
+ ```ruby
748
+ # Backup encryption keys securely
749
+ class EncryptionKeyBackup
750
+ def self.backup_keys_to_vault
751
+ keys = Familia.config.encryption_keys
752
+
753
+ keys.each do |version, key|
754
+ # Store in HashiCorp Vault, AWS KMS, etc.
755
+ VaultClient.store_secret(
756
+ path: "familia/encryption_keys/#{version}",
757
+ data: { key: key },
758
+ lease_duration: '8760h' # 1 year
759
+ )
760
+ end
761
+ end
762
+
763
+ def self.restore_keys_from_vault
764
+ versions = VaultClient.list_secrets('familia/encryption_keys/')
765
+
766
+ restored_keys = {}
767
+ versions.each do |version|
768
+ secret = VaultClient.read_secret("familia/encryption_keys/#{version}")
769
+ restored_keys[version.to_sym] = secret['key']
770
+ end
771
+
772
+ restored_keys
773
+ end
774
+ end
775
+ ```
776
+
777
+ ---
778
+
779
+ ## See Also
780
+
781
+ - **[Overview](../overview.md#encrypted-fields)** - Conceptual introduction to encrypted fields
782
+ - **[Technical Reference](../reference/api-technical.md#encrypted-fields-feature-v200-pre5)** - Implementation details and advanced patterns
783
+ - **[Security Model Guide](security-model.md)** - Cryptographic design and threat model considerations
784
+ - **[Feature System Guide](feature-system.md)** - Understanding Familia's feature architecture
785
+ - **[Implementation Guide](implementation.md)** - Production deployment and configuration patterns