familia 2.0.0.pre19 → 2.0.0.pre22

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/.talismanrc +5 -1
  8. data/CHANGELOG.rst +220 -112
  9. data/CLAUDE.md +28 -1
  10. data/Gemfile +1 -1
  11. data/Gemfile.lock +20 -17
  12. data/bin/try +16 -0
  13. data/bin/tryouts +16 -0
  14. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  15. data/docs/guides/encryption.md +486 -0
  16. data/docs/guides/feature-encrypted-fields.md +123 -7
  17. data/docs/guides/feature-expiration.md +161 -117
  18. data/docs/guides/feature-external-identifiers.md +415 -443
  19. data/docs/guides/feature-object-identifiers.md +400 -269
  20. data/docs/guides/feature-quantization.md +120 -6
  21. data/docs/guides/feature-relationships-indexing.md +318 -0
  22. data/docs/guides/feature-relationships-methods.md +146 -604
  23. data/docs/guides/feature-relationships-participation.md +263 -0
  24. data/docs/guides/feature-relationships.md +118 -136
  25. data/docs/guides/feature-system-devs.md +176 -693
  26. data/docs/guides/feature-system.md +119 -6
  27. data/docs/guides/feature-transient-fields.md +81 -0
  28. data/docs/guides/field-system.md +778 -0
  29. data/docs/guides/index.md +32 -15
  30. data/docs/guides/logging.md +187 -0
  31. data/docs/guides/optimized-loading.md +674 -0
  32. data/docs/guides/thread-safety-monitoring.md +61 -0
  33. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  34. data/docs/migrating/v2.0.0-pre22.md +241 -0
  35. data/docs/overview.md +7 -9
  36. data/docs/reference/api-technical.md +267 -320
  37. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  38. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  39. data/examples/autoloader/mega_customer.rb +2 -0
  40. data/examples/datatype_standalone.rb +4 -3
  41. data/examples/encrypted_fields.rb +2 -1
  42. data/examples/json_usage_patterns.rb +2 -0
  43. data/examples/relationships.rb +3 -0
  44. data/examples/safe_dump.rb +2 -1
  45. data/examples/sampling_demo.rb +53 -0
  46. data/examples/single_connection_transaction_confusions.rb +2 -1
  47. data/familia.gemspec +2 -1
  48. data/lib/familia/base.rb +2 -0
  49. data/lib/familia/connection/behavior.rb +2 -0
  50. data/lib/familia/connection/handlers.rb +2 -0
  51. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  52. data/lib/familia/connection/middleware.rb +34 -24
  53. data/lib/familia/connection/operation_core.rb +3 -2
  54. data/lib/familia/connection/operations.rb +2 -0
  55. data/lib/familia/connection/pipelined_core.rb +3 -3
  56. data/lib/familia/connection/transaction_core.rb +69 -2
  57. data/lib/familia/connection.rb +18 -3
  58. data/lib/familia/data_type/class_methods.rb +3 -1
  59. data/lib/familia/data_type/connection.rb +2 -0
  60. data/lib/familia/data_type/database_commands.rb +2 -0
  61. data/lib/familia/data_type/serialization.rb +79 -52
  62. data/lib/familia/data_type/settings.rb +2 -0
  63. data/lib/familia/data_type/types/counter.rb +2 -0
  64. data/lib/familia/data_type/types/hashkey.rb +7 -5
  65. data/lib/familia/data_type/types/listkey.rb +2 -0
  66. data/lib/familia/data_type/types/lock.rb +2 -0
  67. data/lib/familia/data_type/types/sorted_set.rb +7 -10
  68. data/lib/familia/data_type/types/stringkey.rb +24 -0
  69. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  70. data/lib/familia/data_type.rb +2 -0
  71. data/lib/familia/encryption/encrypted_data.rb +4 -2
  72. data/lib/familia/encryption/manager.rb +2 -0
  73. data/lib/familia/encryption/provider.rb +2 -0
  74. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  75. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  76. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  77. data/lib/familia/encryption/registry.rb +2 -0
  78. data/lib/familia/encryption/request_cache.rb +2 -0
  79. data/lib/familia/encryption.rb +9 -2
  80. data/lib/familia/errors.rb +2 -0
  81. data/lib/familia/features/autoloader.rb +2 -0
  82. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  83. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  84. data/lib/familia/features/encrypted_fields.rb +2 -2
  85. data/lib/familia/features/expiration/extensions.rb +3 -1
  86. data/lib/familia/features/expiration.rb +12 -4
  87. data/lib/familia/features/external_identifier.rb +62 -7
  88. data/lib/familia/features/object_identifier.rb +49 -0
  89. data/lib/familia/features/quantization.rb +3 -1
  90. data/lib/familia/features/relationships/README.md +3 -1
  91. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  92. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
  93. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  94. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +97 -21
  95. data/lib/familia/features/relationships/indexing.rb +3 -0
  96. data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
  97. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  98. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  99. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  100. data/lib/familia/features/relationships/participation.rb +155 -69
  101. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  102. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  103. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  104. data/lib/familia/features/relationships.rb +5 -3
  105. data/lib/familia/features/safe_dump.rb +2 -0
  106. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  107. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  108. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  109. data/lib/familia/features/transient_fields.rb +2 -0
  110. data/lib/familia/features.rb +2 -0
  111. data/lib/familia/field_type.rb +3 -1
  112. data/lib/familia/horreum/connection.rb +17 -1
  113. data/lib/familia/horreum/database_commands.rb +8 -1
  114. data/lib/familia/horreum/definition.rb +16 -6
  115. data/lib/familia/horreum/management.rb +353 -52
  116. data/lib/familia/horreum/persistence.rb +179 -108
  117. data/lib/familia/horreum/related_fields.rb +2 -0
  118. data/lib/familia/horreum/serialization.rb +23 -4
  119. data/lib/familia/horreum/settings.rb +2 -0
  120. data/lib/familia/horreum/utils.rb +2 -0
  121. data/lib/familia/horreum.rb +15 -1
  122. data/lib/familia/identifier_extractor.rb +3 -1
  123. data/lib/familia/instrumentation.rb +156 -0
  124. data/lib/familia/json_serializer.rb +2 -0
  125. data/lib/familia/logging.rb +92 -32
  126. data/lib/familia/refinements/dear_json.rb +2 -0
  127. data/lib/familia/refinements/stylize_words.rb +2 -14
  128. data/lib/familia/refinements/time_literals.rb +2 -0
  129. data/lib/familia/refinements.rb +2 -0
  130. data/lib/familia/secure_identifier.rb +10 -2
  131. data/lib/familia/settings.rb +2 -0
  132. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  133. data/lib/familia/thread_safety/monitor.rb +328 -0
  134. data/lib/familia/utils.rb +13 -0
  135. data/lib/familia/verifiable_identifier.rb +3 -1
  136. data/lib/familia/version.rb +3 -1
  137. data/lib/familia.rb +31 -4
  138. data/lib/middleware/database_command_counter.rb +152 -0
  139. data/lib/middleware/database_logger.rb +295 -170
  140. data/lib/multi_result.rb +61 -31
  141. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  142. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  143. data/try/edge_cases/json_serialization_try.rb +2 -0
  144. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  145. data/try/edge_cases/race_conditions_try.rb +4 -0
  146. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  147. data/try/edge_cases/string_coercion_try.rb +2 -0
  148. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  149. data/try/features/count_any_edge_cases_try.rb +486 -0
  150. data/try/features/count_any_methods_try.rb +197 -0
  151. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  152. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  153. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  154. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  155. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  156. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  157. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  158. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  159. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  160. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  161. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  162. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  163. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  164. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  165. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  166. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  167. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  168. data/try/features/encryption/config_persistence_try.rb +4 -0
  169. data/try/features/encryption/core_try.rb +4 -0
  170. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  171. data/try/features/encryption/module_loading_try.rb +4 -0
  172. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  173. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  174. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  175. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  176. data/try/features/expiration/expiration_try.rb +4 -0
  177. data/try/features/external_identifier/external_identifier_try.rb +305 -8
  178. data/try/features/feature_dependencies_try.rb +2 -0
  179. data/try/features/feature_improvements_try.rb +2 -0
  180. data/try/features/field_groups_try.rb +2 -0
  181. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  182. data/try/features/object_identifier/object_identifier_try.rb +140 -0
  183. data/try/features/quantization/quantization_try.rb +4 -0
  184. data/try/features/real_feature_integration_try.rb +2 -0
  185. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  186. data/try/features/relationships/indexing_rebuild_try.rb +606 -0
  187. data/try/features/relationships/indexing_try.rb +2 -0
  188. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  189. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  190. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  191. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  192. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  193. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  194. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  195. data/try/features/relationships/relationships_api_changes_try.rb +2 -0
  196. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  197. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  198. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  199. data/try/features/relationships/relationships_performance_try.rb +4 -0
  200. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  201. data/try/features/relationships/relationships_try.rb +6 -4
  202. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  203. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  204. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  205. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  206. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  207. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  208. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  209. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  210. data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
  211. data/try/integration/connection/handler_constraints_try.rb +4 -0
  212. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  213. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  214. data/try/integration/connection/operation_mode_guards_try.rb +4 -0
  215. data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
  216. data/try/integration/connection/pools_try.rb +4 -0
  217. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  218. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  219. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  220. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  221. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  222. data/try/integration/connection/transaction_modes_try.rb +4 -0
  223. data/try/integration/conventional_inheritance_try.rb +4 -0
  224. data/try/integration/create_method_try.rb +4 -0
  225. data/try/integration/cross_component_try.rb +4 -0
  226. data/try/integration/data_types/datatype_pipelines_try.rb +9 -3
  227. data/try/integration/data_types/datatype_transactions_try.rb +17 -7
  228. data/try/integration/database_consistency_try.rb +4 -0
  229. data/try/integration/familia_extended_try.rb +4 -0
  230. data/try/integration/familia_members_methods_try.rb +4 -0
  231. data/try/integration/models/customer_safe_dump_try.rb +4 -0
  232. data/try/integration/models/customer_try.rb +7 -3
  233. data/try/integration/models/datatype_base_try.rb +4 -0
  234. data/try/integration/models/familia_object_try.rb +4 -0
  235. data/try/integration/persistence_operations_try.rb +4 -0
  236. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  237. data/try/integration/save_methods_consistency_try.rb +241 -0
  238. data/try/integration/scenarios_try.rb +4 -0
  239. data/try/integration/secure_identifier_try.rb +4 -0
  240. data/try/integration/transaction_safety_core_try.rb +176 -0
  241. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  242. data/try/integration/verifiable_identifier_try.rb +4 -0
  243. data/try/investigation/pipeline_routing/README.md +228 -0
  244. data/try/performance/benchmarks_try.rb +4 -0
  245. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  246. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  247. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  248. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  249. data/try/support/debugging/debug_aad_process.rb +3 -0
  250. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  251. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  252. data/try/support/debugging/debug_context_aad.rb +3 -0
  253. data/try/support/debugging/debug_context_simple.rb +3 -0
  254. data/try/support/debugging/debug_cross_context.rb +3 -0
  255. data/try/support/debugging/debug_database_load.rb +3 -0
  256. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  257. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  258. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  259. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  260. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  261. data/try/support/debugging/debug_load_path.rb +3 -0
  262. data/try/support/debugging/debug_method_definition.rb +3 -0
  263. data/try/support/debugging/debug_method_resolution.rb +3 -0
  264. data/try/support/debugging/debug_minimal.rb +3 -0
  265. data/try/support/debugging/debug_provider.rb +3 -0
  266. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  267. data/try/support/debugging/debug_string_class.rb +3 -0
  268. data/try/support/debugging/debug_test.rb +3 -0
  269. data/try/support/debugging/debug_test_design.rb +3 -0
  270. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  271. data/try/support/debugging/provider_diagnostics.rb +4 -0
  272. data/try/support/helpers/test_cleanup.rb +4 -0
  273. data/try/support/helpers/test_helpers.rb +5 -0
  274. data/try/support/memory/memory_basic_test.rb +4 -0
  275. data/try/support/memory/memory_detailed_test.rb +4 -0
  276. data/try/support/memory/memory_search_for_string.rb +4 -0
  277. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  278. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  279. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  280. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  281. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  282. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  283. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  284. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  285. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  286. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  287. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  288. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  289. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  290. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  291. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  292. data/try/thread_safety/README.md +496 -0
  293. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  294. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  295. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  296. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  297. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  298. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  299. data/try/thread_safety/field_registration_race_try.rb +222 -0
  300. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  301. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  302. data/try/thread_safety/module_config_race_try.rb +175 -0
  303. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  304. data/try/unit/core/autoloader_try.rb +4 -0
  305. data/try/unit/core/base_enhancements_try.rb +4 -0
  306. data/try/unit/core/connection_try.rb +4 -0
  307. data/try/unit/core/errors_try.rb +4 -0
  308. data/try/unit/core/extensions_try.rb +4 -0
  309. data/try/unit/core/familia_logger_try.rb +2 -0
  310. data/try/unit/core/familia_try.rb +4 -0
  311. data/try/unit/core/middleware_sampling_try.rb +335 -0
  312. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  313. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  314. data/try/unit/core/middleware_try.rb +4 -0
  315. data/try/unit/core/settings_try.rb +4 -0
  316. data/try/unit/core/time_utils_try.rb +4 -0
  317. data/try/unit/core/tools_try.rb +4 -0
  318. data/try/unit/core/utils_try.rb +37 -0
  319. data/try/unit/data_types/boolean_try.rb +39 -22
  320. data/try/unit/data_types/counter_try.rb +4 -0
  321. data/try/unit/data_types/datatype_base_try.rb +4 -0
  322. data/try/unit/data_types/hash_try.rb +6 -2
  323. data/try/unit/data_types/list_try.rb +4 -0
  324. data/try/unit/data_types/lock_try.rb +4 -0
  325. data/try/unit/data_types/serialization_try.rb +386 -0
  326. data/try/unit/data_types/sorted_set_try.rb +4 -0
  327. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  328. data/try/unit/data_types/string_try.rb +4 -0
  329. data/try/unit/data_types/unsortedset_try.rb +4 -0
  330. data/try/unit/familia_resolve_class_try.rb +116 -0
  331. data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
  332. data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
  333. data/try/unit/horreum/base_try.rb +4 -0
  334. data/try/unit/horreum/class_methods_try.rb +4 -0
  335. data/try/unit/horreum/commands_try.rb +4 -0
  336. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  337. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +6 -1
  338. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  339. data/try/unit/horreum/field_categories_try.rb +4 -0
  340. data/try/unit/horreum/field_definition_try.rb +4 -0
  341. data/try/unit/horreum/initialization_try.rb +4 -0
  342. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  343. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  344. data/try/unit/horreum/relations_try.rb +4 -0
  345. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  346. data/try/unit/horreum/serialization_try.rb +4 -0
  347. data/try/unit/horreum/settings_try.rb +4 -0
  348. data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
  349. data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
  350. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  351. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  352. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  353. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  354. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  355. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  356. data/try/unit/thread_safety_monitor_try.rb +149 -0
  357. metadata +69 -17
  358. data/.github/workflows/code-quality.yml +0 -138
  359. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
  360. data/changelog.d/20251011_203905_delano_next.rst +0 -30
  361. data/changelog.d/20251011_212633_delano_next.rst +0 -13
  362. data/changelog.d/20251011_221253_delano_next.rst +0 -26
  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
data/bin/tryouts ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'tryouts' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("tryouts", "tryouts")
@@ -0,0 +1,129 @@
1
+ The Naming Problem
2
+
3
+ bidirectional: true is misleading because:
4
+
5
+ 1. It's not truly bidirectional - It only helps you manage membership in specific instances, not query all memberships
6
+ 2. Better name would be: generate_participant_methods: true
7
+ 3. True bidirectionality would mean both sides can easily query their relationships
8
+
9
+ What True Bidirectionality Should Look Like
10
+
11
+ Option 1: Auto-generate reverse collections
12
+
13
+ class Customer < Familia::Horreum
14
+ participates_in Team, :members, bidirectional: true, reverse: :teams
15
+ end
16
+
17
+ # Should generate:
18
+ customer.teams # All teams this customer is in
19
+ customer.teams.count # How many teams
20
+ customer.teams.include?(team_id) # Check membership
21
+
22
+ Option 2: Make bidirectional actually bidirectional
23
+
24
+ class Customer < Familia::Horreum
25
+ participates_in Team, :members, bidirectional: true
26
+ end
27
+
28
+ # Should auto-generate (using pluralized class name):
29
+ customer.teams # Since we're participating in Team class
30
+ customer.organizations # If also participating in Organization class
31
+
32
+ Current Implementation Gap
33
+
34
+ Looking at the actual usage pattern:
35
+ # Easy to go from Team → Customers
36
+ team.members.to_a # Simple!
37
+ customers = Customer.multiget(*team.members.to_a)
38
+
39
+ # Hard to go from Customer → Teams
40
+ customer.participations.members
41
+ .select { |k| k.start_with?("team:") }
42
+ .map { |k| k.split(':')[1] }
43
+ # ... etc - complicated!
44
+
45
+ What's Really Happening
46
+
47
+ The bidirectional flag only controls whether these instance-to-instance methods are generated:
48
+ - customer.add_to_team_members(specific_team)
49
+ - customer.in_team_members?(specific_team)
50
+
51
+ It does NOT create instance-to-collection methods:
52
+ - customer.teams ❌
53
+ - customer.all_team_memberships ❌
54
+
55
+
56
+ The functionality we are hoping to achieve:
57
+ Bidirectional Relationships Feature Spec
58
+
59
+ Problem
60
+
61
+ Currently, Familia relationships are asymmetric. While you can easily query team.members to get all members, there's no convenient way to get
62
+ all teams a user belongs to without manually parsing the participations reverse index.
63
+
64
+ Solution
65
+
66
+ Auto-generate reverse collection methods on participant classes to provide symmetric access to relationships.
67
+
68
+ ## Implemented API (Using _instance Suffix Pattern)
69
+
70
+ class User < Familia::Horreum
71
+ participates_in Team, :members # Auto-generates: user.team_instances
72
+ participates_in Team, :admins # Also adds to: user.team_instances (union)
73
+ participates_in Organization, :employees # Auto-generates: user.organization_instances
74
+ participates_in Organization, :contractors, as: :contracting_orgs # Custom name
75
+ end
76
+
77
+ # Forward (existing)
78
+ team.members # → SortedSet of user IDs
79
+ team.add_members_instance(user) # → Adds to collection + tracks participation
80
+ team.remove_members_instance(user) # → Removes from collection + untracks
81
+
82
+ # Reverse (NEW)
83
+ user.team_instances # → Array of Team instances user belongs to
84
+ user.team_ids # → Array of team IDs (efficient, no loading)
85
+ user.team? # → Boolean: belongs to any teams?
86
+ user.team_count # → Count without loading objects
87
+
88
+ # Custom naming (user chooses base name via as: parameter)
89
+ user.contracting_orgs_instances # → Array of Organization instances
90
+ user.contracting_orgs_ids # → Array of IDs
91
+ user.contracting_orgs? # → Boolean
92
+ user.contracting_orgs_count # → Count
93
+
94
+ ## Naming Rationale: Why `_instance` Suffix?
95
+
96
+ The implementation uses an `_instance` suffix pattern instead of pluralization/singularization to avoid fragility:
97
+
98
+ **Target Methods (Forward Direction):**
99
+ - `team.add_members_instance(user)` instead of `team.add_member(user)`
100
+ - `team.remove_members_instance(user)` instead of `team.remove_member(user)`
101
+
102
+ **Reverse Collection Methods:**
103
+ - `user.team_instances` instead of `user.teams`
104
+ - `user.organization_instances` instead of `user.organizations`
105
+
106
+ **Benefits:**
107
+ 1. **No irregular plurals** - Avoids issues with words like "person/people", "child/children", "foot/feet"
108
+ 2. **Clear intent** - The suffix makes it obvious you're working with instances, not counts or IDs
109
+ 3. **Consistent pattern** - Same suffix for both forward and reverse operations
110
+ 4. **No external dependencies** - Removes need for inflection libraries like `dry-inflector`
111
+ 5. **Predictable** - Easy to remember and document
112
+
113
+ **Trade-off:**
114
+ - Slightly more verbose, but eliminates an entire class of edge case bugs
115
+
116
+ Key Requirements
117
+
118
+ 1. Automatic generation - No manual method definitions needed
119
+ 2. Multiple collections - Union of all collections to same target class
120
+ 3. Performance - Efficient ID-only access without loading objects (no caching for data freshness)
121
+ 4. Custom naming - Override auto-generated names when needed via `as:` parameter
122
+ 5. Thread-safe - No caching means no stale data or cache invalidation complexity
123
+
124
+ Benefits
125
+
126
+ - Symmetry - Both directions equally convenient
127
+ - Discoverability - Natural Ruby method names
128
+ - Efficiency - Choose between full objects, IDs, or counts
129
+ - Backwards compatible - All existing code continues to work
@@ -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
+ ```