familia 2.0.0.pre18 → 2.0.0.pre21

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 (370) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +4 -9
  3. data/.github/workflows/code-smells.yml +64 -3
  4. data/.pre-commit-config.yaml +8 -6
  5. data/.reek.yml +10 -9
  6. data/.rubocop.yml +4 -0
  7. data/CHANGELOG.rst +205 -88
  8. data/CLAUDE.md +62 -10
  9. data/Gemfile +3 -3
  10. data/Gemfile.lock +27 -62
  11. data/README.md +39 -0
  12. data/bin/try +16 -0
  13. data/bin/tryouts +16 -0
  14. data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
  15. data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
  16. data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
  17. data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
  18. data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
  19. data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
  20. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  21. data/docs/guides/encryption.md +486 -0
  22. data/docs/guides/feature-encrypted-fields.md +123 -7
  23. data/docs/guides/feature-expiration.md +177 -133
  24. data/docs/guides/feature-external-identifiers.md +415 -443
  25. data/docs/guides/feature-object-identifiers.md +400 -269
  26. data/docs/guides/feature-quantization.md +120 -6
  27. data/docs/guides/feature-relationships-indexing.md +318 -0
  28. data/docs/guides/feature-relationships-methods.md +146 -604
  29. data/docs/guides/feature-relationships-participation.md +263 -0
  30. data/docs/guides/feature-relationships.md +118 -136
  31. data/docs/guides/feature-system-devs.md +176 -693
  32. data/docs/guides/feature-system.md +119 -6
  33. data/docs/guides/feature-transient-fields.md +81 -0
  34. data/docs/guides/field-system.md +778 -0
  35. data/docs/guides/index.md +32 -15
  36. data/docs/guides/logging.md +187 -0
  37. data/docs/guides/optimized-loading.md +674 -0
  38. data/docs/guides/thread-safety-monitoring.md +61 -0
  39. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  40. data/docs/migrating/v2.0.0-pre19.md +197 -0
  41. data/docs/migrating/v2.0.0-pre22.md +241 -0
  42. data/docs/overview.md +7 -9
  43. data/docs/reference/api-technical.md +267 -320
  44. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  45. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  46. data/examples/autoloader/mega_customer.rb +2 -0
  47. data/examples/datatype_standalone.rb +282 -0
  48. data/examples/encrypted_fields.rb +2 -1
  49. data/examples/json_usage_patterns.rb +2 -0
  50. data/examples/relationships.rb +3 -0
  51. data/examples/safe_dump.rb +2 -1
  52. data/examples/sampling_demo.rb +53 -0
  53. data/examples/single_connection_transaction_confusions.rb +2 -1
  54. data/familia.gemspec +2 -1
  55. data/lib/familia/base.rb +2 -0
  56. data/lib/familia/connection/behavior.rb +254 -0
  57. data/lib/familia/connection/handlers.rb +97 -0
  58. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  59. data/lib/familia/connection/middleware.rb +34 -24
  60. data/lib/familia/connection/operation_core.rb +3 -1
  61. data/lib/familia/connection/operations.rb +2 -0
  62. data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +4 -2
  63. data/lib/familia/connection/transaction_core.rb +75 -9
  64. data/lib/familia/connection.rb +21 -5
  65. data/lib/familia/data_type/class_methods.rb +3 -1
  66. data/lib/familia/data_type/connection.rb +153 -7
  67. data/lib/familia/data_type/database_commands.rb +9 -4
  68. data/lib/familia/data_type/serialization.rb +10 -4
  69. data/lib/familia/data_type/settings.rb +2 -0
  70. data/lib/familia/data_type/types/counter.rb +2 -0
  71. data/lib/familia/data_type/types/hashkey.rb +8 -6
  72. data/lib/familia/data_type/types/listkey.rb +2 -0
  73. data/lib/familia/data_type/types/lock.rb +2 -0
  74. data/lib/familia/data_type/types/sorted_set.rb +2 -0
  75. data/lib/familia/data_type/types/stringkey.rb +2 -0
  76. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  77. data/lib/familia/data_type.rb +2 -0
  78. data/lib/familia/encryption/encrypted_data.rb +4 -2
  79. data/lib/familia/encryption/manager.rb +2 -0
  80. data/lib/familia/encryption/provider.rb +2 -0
  81. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  82. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  83. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  84. data/lib/familia/encryption/registry.rb +2 -0
  85. data/lib/familia/encryption/request_cache.rb +2 -0
  86. data/lib/familia/encryption.rb +9 -2
  87. data/lib/familia/errors.rb +53 -14
  88. data/lib/familia/features/autoloader.rb +2 -0
  89. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  90. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  91. data/lib/familia/features/encrypted_fields.rb +2 -2
  92. data/lib/familia/features/expiration/extensions.rb +11 -11
  93. data/lib/familia/features/expiration.rb +29 -21
  94. data/lib/familia/features/external_identifier.rb +33 -7
  95. data/lib/familia/features/object_identifier.rb +2 -0
  96. data/lib/familia/features/quantization.rb +3 -1
  97. data/lib/familia/features/relationships/README.md +3 -1
  98. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  99. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +177 -47
  100. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  101. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +203 -63
  102. data/lib/familia/features/relationships/indexing.rb +40 -42
  103. data/lib/familia/features/relationships/indexing_relationship.rb +17 -5
  104. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  105. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  106. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  107. data/lib/familia/features/relationships/participation.rb +155 -69
  108. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  109. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  110. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  111. data/lib/familia/features/relationships.rb +5 -3
  112. data/lib/familia/features/safe_dump.rb +2 -0
  113. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  114. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  115. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  116. data/lib/familia/features/transient_fields.rb +2 -0
  117. data/lib/familia/features.rb +2 -0
  118. data/lib/familia/field_type.rb +5 -2
  119. data/lib/familia/horreum/connection.rb +28 -36
  120. data/lib/familia/horreum/database_commands.rb +131 -10
  121. data/lib/familia/horreum/definition.rb +18 -7
  122. data/lib/familia/horreum/management.rb +233 -57
  123. data/lib/familia/horreum/persistence.rb +314 -122
  124. data/lib/familia/horreum/related_fields.rb +2 -0
  125. data/lib/familia/horreum/serialization.rb +26 -4
  126. data/lib/familia/horreum/settings.rb +2 -0
  127. data/lib/familia/horreum/utils.rb +2 -8
  128. data/lib/familia/horreum.rb +46 -13
  129. data/lib/familia/identifier_extractor.rb +2 -0
  130. data/lib/familia/instrumentation.rb +156 -0
  131. data/lib/familia/json_serializer.rb +2 -0
  132. data/lib/familia/logging.rb +94 -37
  133. data/lib/familia/refinements/dear_json.rb +2 -0
  134. data/lib/familia/refinements/stylize_words.rb +2 -14
  135. data/lib/familia/refinements/time_literals.rb +2 -0
  136. data/lib/familia/refinements.rb +2 -0
  137. data/lib/familia/secure_identifier.rb +10 -2
  138. data/lib/familia/settings.rb +9 -7
  139. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  140. data/lib/familia/thread_safety/monitor.rb +328 -0
  141. data/lib/familia/utils.rb +13 -0
  142. data/lib/familia/verifiable_identifier.rb +3 -1
  143. data/lib/familia/version.rb +3 -1
  144. data/lib/familia.rb +31 -4
  145. data/lib/middleware/database_command_counter.rb +152 -0
  146. data/lib/middleware/database_logger.rb +325 -129
  147. data/lib/multi_result.rb +2 -0
  148. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  149. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  150. data/try/edge_cases/json_serialization_try.rb +2 -0
  151. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  152. data/try/edge_cases/race_conditions_try.rb +4 -0
  153. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  154. data/try/edge_cases/string_coercion_try.rb +6 -4
  155. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  156. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  157. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  158. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  159. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  160. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  161. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  162. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  163. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  164. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  165. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  166. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  167. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  168. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  169. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  170. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  171. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  172. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  173. data/try/features/encryption/config_persistence_try.rb +4 -0
  174. data/try/features/encryption/core_try.rb +4 -0
  175. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  176. data/try/features/encryption/module_loading_try.rb +4 -0
  177. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  178. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  179. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  180. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  181. data/try/features/expiration/expiration_try.rb +5 -1
  182. data/try/features/external_identifier/external_identifier_try.rb +171 -8
  183. data/try/features/feature_dependencies_try.rb +2 -0
  184. data/try/features/feature_improvements_try.rb +2 -0
  185. data/try/features/field_groups_try.rb +2 -0
  186. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  187. data/try/features/object_identifier/object_identifier_try.rb +2 -0
  188. data/try/features/quantization/quantization_try.rb +4 -0
  189. data/try/features/real_feature_integration_try.rb +2 -0
  190. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  191. data/try/features/relationships/indexing_rebuild_try.rb +600 -0
  192. data/try/features/relationships/indexing_try.rb +30 -4
  193. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  194. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  195. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  196. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  197. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  198. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  199. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  200. data/try/features/relationships/relationships_api_changes_try.rb +6 -4
  201. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  202. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  203. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  204. data/try/features/relationships/relationships_performance_try.rb +4 -0
  205. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  206. data/try/features/relationships/relationships_try.rb +6 -4
  207. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  208. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  209. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  210. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  211. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  212. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  213. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  214. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  215. data/try/integration/connection/fiber_context_preservation_try.rb +7 -3
  216. data/try/integration/connection/handler_constraints_try.rb +4 -0
  217. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  218. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  219. data/try/integration/connection/operation_mode_guards_try.rb +5 -1
  220. data/try/integration/connection/pipeline_fallback_integration_try.rb +15 -12
  221. data/try/integration/connection/pools_try.rb +4 -0
  222. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  223. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  224. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  225. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  226. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  227. data/try/integration/connection/transaction_modes_try.rb +4 -0
  228. data/try/integration/conventional_inheritance_try.rb +4 -0
  229. data/try/integration/create_method_try.rb +26 -22
  230. data/try/integration/cross_component_try.rb +4 -0
  231. data/try/integration/data_types/datatype_pipelines_try.rb +108 -0
  232. data/try/integration/data_types/datatype_transactions_try.rb +251 -0
  233. data/try/integration/database_consistency_try.rb +4 -0
  234. data/try/integration/familia_extended_try.rb +4 -0
  235. data/try/integration/familia_members_methods_try.rb +4 -0
  236. data/try/integration/models/customer_safe_dump_try.rb +9 -1
  237. data/try/integration/models/customer_try.rb +4 -0
  238. data/try/integration/models/datatype_base_try.rb +4 -0
  239. data/try/integration/models/familia_object_try.rb +5 -1
  240. data/try/integration/persistence_operations_try.rb +166 -10
  241. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  242. data/try/integration/save_methods_consistency_try.rb +241 -0
  243. data/try/integration/scenarios_try.rb +4 -0
  244. data/try/integration/secure_identifier_try.rb +4 -0
  245. data/try/integration/transaction_safety_core_try.rb +176 -0
  246. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  247. data/try/integration/verifiable_identifier_try.rb +4 -0
  248. data/try/investigation/pipeline_routing/README.md +228 -0
  249. data/try/performance/benchmarks_try.rb +4 -0
  250. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  251. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  252. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  253. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  254. data/try/support/debugging/debug_aad_process.rb +3 -0
  255. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  256. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  257. data/try/support/debugging/debug_context_aad.rb +3 -0
  258. data/try/support/debugging/debug_context_simple.rb +3 -0
  259. data/try/support/debugging/debug_cross_context.rb +3 -0
  260. data/try/support/debugging/debug_database_load.rb +3 -0
  261. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  262. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  263. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  264. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  265. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  266. data/try/support/debugging/debug_load_path.rb +3 -0
  267. data/try/support/debugging/debug_method_definition.rb +3 -0
  268. data/try/support/debugging/debug_method_resolution.rb +3 -0
  269. data/try/support/debugging/debug_minimal.rb +3 -0
  270. data/try/support/debugging/debug_provider.rb +3 -0
  271. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  272. data/try/support/debugging/debug_string_class.rb +3 -0
  273. data/try/support/debugging/debug_test.rb +3 -0
  274. data/try/support/debugging/debug_test_design.rb +3 -0
  275. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  276. data/try/support/debugging/provider_diagnostics.rb +4 -0
  277. data/try/support/helpers/test_cleanup.rb +4 -0
  278. data/try/support/helpers/test_helpers.rb +5 -0
  279. data/try/support/memory/memory_basic_test.rb +4 -0
  280. data/try/support/memory/memory_detailed_test.rb +4 -0
  281. data/try/support/memory/memory_search_for_string.rb +4 -0
  282. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  283. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  284. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  285. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  286. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  287. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  288. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  289. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  290. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  291. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  292. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  293. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  294. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  295. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  296. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  297. data/try/thread_safety/README.md +496 -0
  298. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  299. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  300. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  301. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  302. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  303. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  304. data/try/thread_safety/field_registration_race_try.rb +222 -0
  305. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  306. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  307. data/try/thread_safety/module_config_race_try.rb +175 -0
  308. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  309. data/try/unit/core/autoloader_try.rb +4 -0
  310. data/try/unit/core/base_enhancements_try.rb +4 -0
  311. data/try/unit/core/connection_try.rb +4 -0
  312. data/try/unit/core/errors_try.rb +4 -0
  313. data/try/unit/core/extensions_try.rb +4 -0
  314. data/try/unit/core/familia_logger_try.rb +2 -0
  315. data/try/unit/core/familia_try.rb +4 -0
  316. data/try/unit/core/middleware_sampling_try.rb +335 -0
  317. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  318. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  319. data/try/unit/core/middleware_try.rb +4 -0
  320. data/try/unit/core/settings_try.rb +4 -0
  321. data/try/unit/core/time_utils_try.rb +4 -0
  322. data/try/unit/core/tools_try.rb +4 -0
  323. data/try/unit/core/utils_try.rb +37 -0
  324. data/try/unit/data_types/boolean_try.rb +5 -1
  325. data/try/unit/data_types/counter_try.rb +4 -0
  326. data/try/unit/data_types/datatype_base_try.rb +4 -0
  327. data/try/unit/data_types/hash_try.rb +4 -0
  328. data/try/unit/data_types/list_try.rb +4 -0
  329. data/try/unit/data_types/lock_try.rb +4 -0
  330. data/try/unit/data_types/sorted_set_try.rb +4 -0
  331. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  332. data/try/unit/data_types/string_try.rb +5 -1
  333. data/try/unit/data_types/unsortedset_try.rb +4 -0
  334. data/try/unit/familia_resolve_class_try.rb +116 -0
  335. data/try/unit/horreum/auto_indexing_on_save_try.rb +36 -16
  336. data/try/unit/horreum/automatic_index_validation_try.rb +255 -0
  337. data/try/unit/horreum/base_try.rb +5 -1
  338. data/try/unit/horreum/class_methods_try.rb +6 -2
  339. data/try/unit/horreum/commands_try.rb +4 -0
  340. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  341. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
  342. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  343. data/try/unit/horreum/field_categories_try.rb +4 -0
  344. data/try/unit/horreum/field_definition_try.rb +4 -0
  345. data/try/unit/horreum/initialization_try.rb +5 -1
  346. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  347. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  348. data/try/unit/horreum/relations_try.rb +8 -4
  349. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  350. data/try/unit/horreum/serialization_try.rb +6 -2
  351. data/try/unit/horreum/settings_try.rb +4 -0
  352. data/try/unit/horreum/unique_index_edge_cases_try.rb +380 -0
  353. data/try/unit/horreum/unique_index_guard_validation_try.rb +283 -0
  354. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  355. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  356. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  357. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  358. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  359. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  360. data/try/unit/thread_safety_monitor_try.rb +149 -0
  361. metadata +81 -14
  362. data/.github/workflows/code-quality.yml +0 -138
  363. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  364. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  365. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  366. data/docs/archive/README.md +0 -64
  367. data/docs/archive/api-reference.md +0 -333
  368. data/docs/guides/core-field-system.md +0 -806
  369. data/docs/guides/implementation.md +0 -276
  370. data/docs/guides/security-model.md +0 -183
@@ -0,0 +1,486 @@
1
+ # Implementation Guide
2
+
3
+ ## Architecture Overview
4
+
5
+ The encrypted fields feature uses a modular provider system with field transformation hooks:
6
+
7
+ ```
8
+ User Input → Field Setter → Provider Selection → Encryption → Valkey/Redis
9
+ Valkey/Redis → Algorithm Detection → Decryption → Field Getter → User Output
10
+ ```
11
+
12
+ ### Provider Architecture
13
+
14
+ ```
15
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
16
+ │ Manager │ │ Registry │ │ Providers │
17
+ │ │ │ │ │ │
18
+ │ - encrypt() │───→│ - get() │───→│ XChaCha20Poly │
19
+ │ - decrypt() │ │ - register() │ │ AES-GCM │
20
+ │ - derive_key() │ │ - available() │ │ │
21
+ └─────────────────┘ └──────────────────┘ └─────────────────┘
22
+ ```
23
+
24
+ ## Core Components
25
+
26
+ ### 1. Registry System
27
+
28
+ The Registry manages available encryption providers and selects the best one:
29
+
30
+ ```ruby
31
+ module Familia::Encryption::Registry
32
+ # Auto-register available providers by priority
33
+ def self.setup!
34
+
35
+ # Get provider instance by algorithm
36
+ def self.get(algorithm)
37
+
38
+ # Get highest-priority available provider
39
+ def self.default_provider
40
+
41
+ # Get available algorithm names
42
+ def self.available_algorithms
43
+ end
44
+ ```
45
+
46
+ ### 2. Manager Class
47
+
48
+ The Manager handles encryption/decryption operations with provider delegation:
49
+
50
+ ```ruby
51
+ class Familia::Encryption::Manager
52
+ # Use specific algorithm or auto-select best
53
+ def initialize(algorithm: nil)
54
+
55
+ # Encrypt with context-specific key derivation
56
+ def encrypt(plaintext, context:, additional_data: nil)
57
+
58
+ # Decrypt with automatic algorithm detection
59
+ def decrypt(encrypted_json, context:, additional_data: nil)
60
+ end
61
+ ```
62
+
63
+ ### 3. Provider Interface
64
+
65
+ All providers implement a common interface:
66
+
67
+ ```ruby
68
+ class Provider
69
+ ALGORITHM = 'algorithm-name'
70
+ NONCE_SIZE = 12 # or 24 for XChaCha20
71
+ AUTH_TAG_SIZE = 16
72
+
73
+ def self.available? # Check if dependencies are met
74
+ def self.priority # Higher = preferred (XChaCha20: 100, AES: 50)
75
+
76
+ def encrypt(plaintext, key, additional_data)
77
+ def decrypt(ciphertext, key, nonce, auth_tag, additional_data)
78
+ def derive_key(master_key, context, personal: nil)
79
+ def generate_nonce
80
+ end
81
+ ```
82
+
83
+ ### 4. Key Derivation
84
+
85
+ Each field gets a unique encryption key using provider-specific methods:
86
+
87
+ ```
88
+ Master Key + Field Context → Provider KDF → Field-Specific Key
89
+
90
+ XChaCha20-Poly1305: BLAKE2b with personalization
91
+ AES-256-GCM: HKDF-SHA256
92
+ ```
93
+
94
+ ## Implementation Steps
95
+
96
+ ### Step 1: Enable Encryption
97
+
98
+ ```ruby
99
+ class MyModel < Familia::Horreum
100
+ # Add the feature
101
+ feature :encrypted_fields
102
+
103
+ # Define encrypted fields
104
+ encrypted_field :sensitive_data
105
+ encrypted_field :api_key
106
+ end
107
+ ```
108
+
109
+ ### Step 2: Configure Keys
110
+
111
+ ```ruby
112
+ # config/initializers/familia.rb
113
+ Familia.configure do |config|
114
+ config.encryption_keys = {
115
+ v1: ENV['FAMILIA_ENCRYPTION_KEY_V1']
116
+ }
117
+ config.current_key_version = :v1
118
+ config.encryption_personalization = 'MyApp-2024' # Optional
119
+ end
120
+
121
+ # Validate configuration at startup
122
+ Familia::Encryption.validate_configuration!
123
+ ```
124
+
125
+ ### Step 3: Generate Keys
126
+
127
+ ```bash
128
+ # Generate a secure 256-bit key (32 bytes)
129
+ $ openssl rand -base64 32
130
+ # => base64_encoded_key_here
131
+
132
+ # Add to environment
133
+ $ echo "FAMILIA_ENCRYPTION_KEY_V1=base64_encoded_key_here" >> .env
134
+ ```
135
+
136
+ ### Step 4: Install Optional Dependencies
137
+
138
+ For best security and performance, install RbNaCl:
139
+
140
+ ```bash
141
+ # Add to Gemfile
142
+ gem 'rbnacl', '~> 7.1', '>= 7.1.1'
143
+
144
+ # Install
145
+ $ bundle install
146
+ ```
147
+
148
+ Without RbNaCl, Familia falls back to OpenSSL AES-256-GCM (still secure but lower priority).
149
+
150
+ ## Advanced Usage
151
+
152
+ ### Additional Authenticated Data (AAD)
153
+
154
+ ```ruby
155
+ class SecureDocument < Familia::Horreum
156
+ feature :encrypted_fields
157
+
158
+ field :doc_id, :owner_id, :classification
159
+ encrypted_field :content, aad_fields: [:doc_id, :owner_id, :classification]
160
+ end
161
+
162
+ # The content can only be decrypted if doc_id, owner_id, and classification
163
+ # values match those used during encryption
164
+ ```
165
+
166
+ ### Request-Level Caching
167
+
168
+ ```ruby
169
+ # For performance optimization
170
+ Familia::Encryption.with_request_cache do
171
+ vault.secret_key = "value1"
172
+ vault.api_token = "value2"
173
+ vault.save # Reuses derived keys within this block
174
+ end
175
+
176
+ # Cache is automatically cleared when block exits
177
+ # Or manually: Familia::Encryption.clear_request_cache!
178
+ ```
179
+
180
+ ### ConcealedString Objects
181
+
182
+ Encrypted fields return ConcealedString objects to prevent accidental exposure:
183
+
184
+ ```ruby
185
+ secret = vault.secret_key
186
+ secret.class # => ConcealedString
187
+ puts secret # => "[CONCEALED]" (automatic redaction)
188
+ secret.inspect # => "[CONCEALED]" (automatic redaction)
189
+
190
+ # Safe access pattern - requires explicit reveal
191
+ secret.reveal do |raw_value|
192
+ # Use raw_value carefully - avoid creating copies
193
+ HTTP.post('/api', headers: { 'X-Token' => raw_value })
194
+ end
195
+
196
+ # Check if cleared from memory
197
+ secret.cleared? # Returns true if wiped
198
+
199
+ # Explicit cleanup
200
+ secret.clear! # Best-effort memory wiping
201
+ ```
202
+
203
+ ## Provider-Specific Features
204
+
205
+ ### XChaCha20-Poly1305 Provider (Recommended)
206
+
207
+ ```ruby
208
+ # Enable with RbNaCl gem
209
+ gem 'rbnacl', '~> 7.1'
210
+
211
+ # Benefits:
212
+ # - Extended nonce (192 bits vs 96 bits)
213
+ # - Better resistance to nonce reuse
214
+ # - BLAKE2b key derivation with personalization
215
+ # - Priority: 100 (highest)
216
+ ```
217
+
218
+ ### AES-256-GCM Provider (Fallback)
219
+
220
+ ```ruby
221
+ # Always available with OpenSSL
222
+ # - 256-bit keys, 96-bit nonces (12 bytes)
223
+ # - HKDF-SHA256 key derivation
224
+ # - Priority: 50
225
+ # - Good compatibility, proven security
226
+ ```
227
+
228
+ ## Performance Optimization
229
+
230
+ ### Provider Benchmarking
231
+
232
+ ```ruby
233
+ # Compare provider performance
234
+ results = Familia::Encryption.benchmark(iterations: 1000)
235
+ puts results
236
+ # => {
237
+ # "xchacha20poly1305" => { time: 0.45, ops_per_sec: 4444, priority: 100 },
238
+ # "aes-256-gcm" => { time: 0.52, ops_per_sec: 3846, priority: 50 }
239
+ # }
240
+ ```
241
+
242
+ ### Monitoring Key Derivation
243
+
244
+ ```ruby
245
+ # Monitor key derivations (should increment with each operation)
246
+ puts Familia::Encryption.derivation_count.value
247
+ # => 42
248
+
249
+ # Reset counter for testing
250
+ Familia::Encryption.reset_derivation_count!
251
+ ```
252
+
253
+ ### Encryption Status
254
+
255
+ ```ruby
256
+ # Get current encryption setup info
257
+ status = Familia::Encryption.status
258
+ # => {
259
+ # default_algorithm: "xchacha20poly1305",
260
+ # available_algorithms: ["xchacha20poly1305", "aes-256-gcm"],
261
+ # preferred_available: "Familia::Encryption::Providers::XChaCha20Poly1305Provider",
262
+ # using_hardware: false,
263
+ # key_versions: [:v1, :v2],
264
+ # current_version: :v2
265
+ # }
266
+ ```
267
+
268
+ ## Field-Level Features
269
+
270
+ ### Instance Methods
271
+
272
+ ```ruby
273
+ vault = Vault.new(secret_key: 'secret', api_token: 'token123')
274
+
275
+ # Check if any encrypted fields have values
276
+ vault.encrypted_data? # => true
277
+
278
+ # Clear all encrypted field values from memory
279
+ vault.clear_encrypted_fields!
280
+
281
+ # Check if all encrypted fields have been cleared
282
+ vault.encrypted_fields_cleared? # => true
283
+
284
+ # Re-encrypt all fields with current settings (for key rotation)
285
+ vault.re_encrypt_fields!
286
+ vault.save
287
+
288
+ # Get encryption status for all encrypted fields
289
+ status = vault.encrypted_fields_status
290
+ # => {
291
+ # secret_key: { encrypted: true, algorithm: "xchacha20poly1305", cleared: false },
292
+ # api_token: { encrypted: true, cleared: true }
293
+ # }
294
+ ```
295
+
296
+ ### Class Methods
297
+
298
+ ```ruby
299
+ class Vault < Familia::Horreum
300
+ feature :encrypted_fields
301
+ encrypted_field :secret_key
302
+ encrypted_field :api_token
303
+ end
304
+
305
+ # Get list of encrypted field names
306
+ Vault.encrypted_fields # => [:secret_key, :api_token]
307
+
308
+ # Check if a field is encrypted
309
+ Vault.encrypted_field?(:secret_key) # => true
310
+ Vault.encrypted_field?(:name) # => false
311
+ ```
312
+
313
+ ## Key Rotation
314
+
315
+ The feature supports key versioning for seamless key rotation:
316
+
317
+ ```ruby
318
+ # Step 1: Add new key version while keeping old keys
319
+ Familia.configure do |config|
320
+ config.encryption_keys = {
321
+ v1: old_key,
322
+ v2: new_key
323
+ }
324
+ config.current_key_version = :v2
325
+ end
326
+
327
+ # Step 2: Objects decrypt with any valid key, encrypt with current key
328
+ vault.secret_key = "new-secret" # Encrypted with v2 key
329
+ vault.save
330
+
331
+ # Step 3: Re-encrypt existing records
332
+ vault.re_encrypt_fields! # Uses current key version
333
+ vault.save
334
+
335
+ # Step 4: After all data is re-encrypted, remove old key
336
+ ```
337
+
338
+ ## Error Handling
339
+
340
+ The feature provides specific error types for different failure modes:
341
+
342
+ ```ruby
343
+ # Invalid ciphertext or tampering
344
+ begin
345
+ vault.secret_key.reveal { |s| s }
346
+ rescue Familia::EncryptionError => e
347
+ # "Decryption failed - invalid key or corrupted data"
348
+ end
349
+
350
+ # Missing encryption configuration
351
+ Familia.config.encryption_keys = {}
352
+ begin
353
+ vault.secret_key.reveal { |s| s }
354
+ rescue Familia::EncryptionError => e
355
+ # "No encryption keys configured"
356
+ end
357
+
358
+ # Invalid key version
359
+ begin
360
+ vault.secret_key.reveal { |s| s }
361
+ rescue Familia::EncryptionError => e
362
+ # "No key for version: v1"
363
+ end
364
+ ```
365
+
366
+ ## Testing
367
+
368
+ ```ruby
369
+ # Test helper setup
370
+ Familia.config.encryption_keys = { v1: Base64.strict_encode64('a' * 32) }
371
+ Familia.config.current_key_version = :v1
372
+
373
+ # In tests
374
+ it "encrypts sensitive fields" do
375
+ user = User.create(api_token: "secret-token")
376
+
377
+ # Verify encryption in Redis
378
+ raw_value = redis.hget(user.dbkey, "api_token")
379
+ expect(raw_value).not_to include("secret-token")
380
+
381
+ encrypted_data = JSON.parse(raw_value)
382
+ expect(encrypted_data).to have_key("ciphertext")
383
+ expect(encrypted_data).to have_key("algorithm")
384
+ end
385
+
386
+ it "provides concealed string access" do
387
+ user = User.create(api_token: "secret-token")
388
+ concealed = user.api_token
389
+
390
+ expect(concealed).to be_a(ConcealedString)
391
+ expect(concealed.to_s).to eq("[CONCEALED]")
392
+
393
+ concealed.reveal do |token|
394
+ expect(token).to eq("secret-token")
395
+ end
396
+ end
397
+ ```
398
+
399
+ ## Security Model
400
+
401
+ ### Ciphertext Format
402
+
403
+ Encrypted data is stored as JSON with algorithm-specific metadata:
404
+
405
+ ```json
406
+ {
407
+ "algorithm": "xchacha20poly1305",
408
+ "nonce": "base64_encoded_nonce",
409
+ "ciphertext": "base64_encoded_data",
410
+ "auth_tag": "base64_encoded_tag",
411
+ "key_version": "v1"
412
+ }
413
+ ```
414
+
415
+ ### Memory Safety Limitations
416
+
417
+ ⚠️ **Important**: Ruby provides NO memory safety guarantees:
418
+ - No secure memory wiping (best-effort only)
419
+ - Garbage collector may copy secrets
420
+ - String operations create uncontrolled copies
421
+ - Memory dumps may contain plaintext secrets
422
+
423
+ For highly sensitive applications, consider:
424
+ - External key management (HashiCorp Vault, AWS KMS)
425
+ - Hardware Security Modules (HSMs)
426
+ - Languages with secure memory handling
427
+ - Dedicated cryptographic appliances
428
+
429
+ ### Threat Model
430
+
431
+ ✅ **Protected Against:**
432
+ - Database compromise (encrypted data only)
433
+ - Field value swapping (field-specific keys)
434
+ - Cross-record attacks (record-specific keys)
435
+ - Tampering (authenticated encryption)
436
+
437
+ ❌ **Not Protected Against:**
438
+ - Master key compromise (all data compromised)
439
+ - Application memory compromise (plaintext in RAM)
440
+ - Side-channel attacks (timing, power analysis)
441
+ - Insider threats with application access
442
+
443
+ ## Troubleshooting
444
+
445
+ ### Common Issues
446
+
447
+ 1. **"No encryption key configured"**
448
+ - Ensure `FAMILIA_ENCRYPTION_KEY` is set
449
+ - Check `Familia.config.encryption_keys`
450
+
451
+ 2. **"Decryption failed"**
452
+ - Verify correct key version
453
+ - Check if data was encrypted with different key
454
+ - Ensure AAD fields haven't changed
455
+
456
+ 3. **Performance degradation**
457
+ - Enable request-level caching with `with_request_cache`
458
+ - Consider installing RbNaCl gem for XChaCha20
459
+
460
+ 4. **Provider not available**
461
+ - Install RbNaCl for XChaCha20: `gem install rbnacl`
462
+ - Falls back to AES-256-GCM automatically
463
+
464
+ ## API Reference
465
+
466
+ ### Module Methods
467
+
468
+ ```ruby
469
+ # Main encryption/decryption
470
+ Familia::Encryption.encrypt(plaintext, context:, additional_data: nil)
471
+ Familia::Encryption.decrypt(encrypted_json, context:, additional_data: nil)
472
+ Familia::Encryption.encrypt_with(algorithm, plaintext, context:, additional_data: nil)
473
+
474
+ # Configuration and status
475
+ Familia::Encryption.validate_configuration!
476
+ Familia::Encryption.status
477
+ Familia::Encryption.benchmark(iterations: 1000)
478
+
479
+ # Request caching
480
+ Familia::Encryption.with_request_cache { block }
481
+ Familia::Encryption.clear_request_cache!
482
+
483
+ # Monitoring
484
+ Familia::Encryption.derivation_count
485
+ Familia::Encryption.reset_derivation_count!
486
+ ```
@@ -296,8 +296,11 @@ logger.info("User key: #{user.api_key}") # => "User key: [CONCEALED]"
296
296
  user_json = user.to_json
297
297
  # All encrypted fields appear as "[CONCEALED]" in JSON
298
298
 
299
- # Explicit access when needed
300
- actual_key = user.api_key.reveal # => "sk-1234567890abcdef"
299
+ # Explicit access when needed (requires block)
300
+ user.api_key.reveal do |actual_key|
301
+ # Use actual_key here: "sk-1234567890abcdef"
302
+ process_key(actual_key)
303
+ end
301
304
  ```
302
305
 
303
306
  ### String Operations
@@ -313,15 +316,16 @@ api_key.size # => 11
313
316
  api_key == "[CONCEALED]" # => true
314
317
  api_key.start_with?("[CONCEALED]") # => true
315
318
 
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
319
+ # Reveal for actual operations (requires block)
320
+ api_key.reveal do |actual_key|
321
+ actual_key.length # => 17 (actual key length)
322
+ actual_key.start_with?("sk-") # => true
323
+ end
320
324
  ```
321
325
 
322
326
  > **⚠️ Important**
323
327
  >
324
- > Always use `.reveal` explicitly when you need the actual value. This makes it obvious in code reviews where sensitive data is being accessed.
328
+ > Always use `.reveal { |value| ... }` with a block when you need the actual value. This makes it obvious in code reviews where sensitive data is being accessed and prevents accidental copies.
325
329
 
326
330
  ## Performance Optimization
327
331
 
@@ -683,6 +687,97 @@ class FastEncryptedModelTest < Minitest::Test
683
687
  end
684
688
  ```
685
689
 
690
+ ## Instance Methods
691
+
692
+ ### Core Encrypted Field Methods
693
+
694
+ #### `encrypted_data?`
695
+ Check if instance has any encrypted fields with values.
696
+
697
+ ```ruby
698
+ vault = Vault.new(secret_key: "value")
699
+ vault.encrypted_data? # => true
700
+
701
+ empty_vault = Vault.new
702
+ empty_vault.encrypted_data? # => false
703
+ ```
704
+
705
+ #### `clear_encrypted_fields!`
706
+ Clear all encrypted field values from memory.
707
+
708
+ ```ruby
709
+ vault.clear_encrypted_fields!
710
+ vault.encrypted_fields_cleared? # => true
711
+ ```
712
+
713
+ #### `re_encrypt_fields!`
714
+ Re-encrypt all encrypted fields with current encryption settings (useful for key rotation).
715
+
716
+ ```ruby
717
+ vault.re_encrypt_fields!
718
+ vault.save # Persists re-encrypted data
719
+ ```
720
+
721
+ #### `encrypted_fields_status`
722
+ Get encryption status for all encrypted fields.
723
+
724
+ ```ruby
725
+ vault.encrypted_fields_status
726
+ # => {
727
+ # secret_key: { encrypted: true, algorithm: "xchacha20poly1305", cleared: false },
728
+ # api_token: { encrypted: true, cleared: true }
729
+ # }
730
+ ```
731
+
732
+ ### Class Methods
733
+
734
+ #### `encrypted_field?(field_name)`
735
+ Check if a field is encrypted.
736
+
737
+ ```ruby
738
+ Vault.encrypted_field?(:secret_key) # => true
739
+ Vault.encrypted_field?(:name) # => false
740
+ ```
741
+
742
+ #### `encryption_info`
743
+ Get encryption algorithm information.
744
+
745
+ ```ruby
746
+ Vault.encryption_info
747
+ # => {
748
+ # algorithm: "xchacha20poly1305",
749
+ # key_size: 32,
750
+ # nonce_size: 24,
751
+ # tag_size: 16
752
+ # }
753
+ ```
754
+
755
+ ### ConcealedString Methods
756
+
757
+ #### `reveal { |plaintext| ... }`
758
+ Primary API for accessing decrypted values (requires block).
759
+
760
+ ```ruby
761
+ user.api_token.reveal do |token|
762
+ HTTP.post('/api', headers: { 'X-Token' => token })
763
+ end
764
+ ```
765
+
766
+ #### `belongs_to_context?(record, field_name)`
767
+ Validate that ConcealedString belongs to the given record context.
768
+
769
+ ```ruby
770
+ concealed.belongs_to_context?(user, :api_token) # => true/false
771
+ ```
772
+
773
+ #### `cleared?` and `clear!`
774
+ Memory management for encrypted data.
775
+
776
+ ```ruby
777
+ concealed.clear!
778
+ concealed.cleared? # => true
779
+ ```
780
+
686
781
  ## Production Considerations
687
782
 
688
783
  ### Monitoring and Alerting
@@ -776,6 +871,27 @@ end
776
871
 
777
872
  ---
778
873
 
874
+ ## Configuration Reference
875
+
876
+ ### Additional Configuration Options
877
+
878
+ ```ruby
879
+ Familia.configure do |config|
880
+ config.encryption_keys = { v1: key, v2: new_key }
881
+ config.current_key_version = :v2
882
+ config.encryption_personalization = 'MyApp-2024' # XChaCha20 only
883
+ end
884
+
885
+ # Validate configuration
886
+ Familia::Encryption.validate_configuration!
887
+ ```
888
+
889
+ ### Error Types
890
+
891
+ - `Familia::EncryptionError` - General encryption/decryption failures
892
+ - `Familia::SerializerError` - Serialization safety violations
893
+ - `SecurityError` - Context validation or cleared data access
894
+
779
895
  ## See Also
780
896
 
781
897
  - **[Overview](../overview.md#encrypted-fields)** - Conceptual introduction to encrypted fields