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
@@ -0,0 +1,156 @@
1
+ # lib/familia/instrumentation.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require 'concurrent-ruby'
6
+
7
+ module Familia
8
+ # Provides instrumentation hooks for observability into Familia operations.
9
+ #
10
+ # This module allows applications to register callbacks for various events
11
+ # in Familia's lifecycle, enabling audit trails, performance monitoring,
12
+ # and operational observability.
13
+ #
14
+ # @example Basic usage
15
+ # Familia.on_command do |cmd, duration, context|
16
+ # puts "Redis command: #{cmd} (#{duration}μs)"
17
+ # end
18
+ #
19
+ # @example Audit trail for secrets service
20
+ # Familia.on_lifecycle do |event, instance, context|
21
+ # case event
22
+ # when :save
23
+ # AuditLog.create!(
24
+ # event: 'secret_saved',
25
+ # secret_id: instance.identifier,
26
+ # user_id: RequestContext.current_user_id
27
+ # )
28
+ # end
29
+ # end
30
+ #
31
+ module Instrumentation
32
+ @hooks = {
33
+ command: Concurrent::Array.new,
34
+ pipeline: Concurrent::Array.new,
35
+ lifecycle: Concurrent::Array.new,
36
+ error: Concurrent::Array.new
37
+ }
38
+
39
+ class << self
40
+ # Register a callback for Redis command execution.
41
+ #
42
+ # @yield [cmd, duration, context] Callback block
43
+ # @yieldparam cmd [String] The Redis command name (e.g., "SET", "ZADD")
44
+ # @yieldparam duration [Integer] Command execution duration in microseconds
45
+ # @yieldparam context [Hash] Additional context including:
46
+ # - :full_command [Array] Complete command with arguments
47
+ # - :db [Integer] Database number
48
+ # - :connection_id [String] Connection identifier
49
+ #
50
+ # @example
51
+ # Familia.on_command do |cmd, duration, ctx|
52
+ # StatsD.timing("familia.command.#{cmd.downcase}", duration / 1000.0)
53
+ # end
54
+ #
55
+ def on_command(&block)
56
+ @hooks[:command] << block
57
+ end
58
+
59
+ # Register a callback for pipelined Redis operations.
60
+ #
61
+ # @yield [command_count, duration, context] Callback block
62
+ # @yieldparam command_count [Integer] Number of commands in the pipeline
63
+ # @yieldparam duration [Integer] Pipeline execution duration in microseconds
64
+ # @yieldparam context [Hash] Additional context
65
+ #
66
+ # @example
67
+ # Familia.on_pipeline do |count, duration, ctx|
68
+ # StatsD.timing("familia.pipeline", duration / 1000.0)
69
+ # StatsD.gauge("familia.pipeline.commands", count)
70
+ # end
71
+ #
72
+ def on_pipeline(&block)
73
+ @hooks[:pipeline] << block
74
+ end
75
+
76
+ # Register a callback for Horreum lifecycle events.
77
+ #
78
+ # @yield [event, instance, context] Callback block
79
+ # @yieldparam event [Symbol] Lifecycle event (:initialize, :save, :destroy)
80
+ # @yieldparam instance [Familia::Horreum] The object instance
81
+ # @yieldparam context [Hash] Additional context including:
82
+ # - :duration [Integer] Operation duration in microseconds (for initialize/save)
83
+ # - :update_expiration [Boolean] Whether TTL was updated (for save)
84
+ #
85
+ # @example
86
+ # Familia.on_lifecycle do |event, instance, ctx|
87
+ # case event
88
+ # when :destroy
89
+ # Rails.logger.info("Destroyed #{instance.class}:#{instance.identifier}")
90
+ # end
91
+ # end
92
+ #
93
+ def on_lifecycle(&block)
94
+ @hooks[:lifecycle] << block
95
+ end
96
+
97
+ # Register a callback for error conditions.
98
+ #
99
+ # @yield [error, context] Callback block
100
+ # @yieldparam error [Exception] The error that occurred
101
+ # @yieldparam context [Hash] Additional context including:
102
+ # - :operation [Symbol] Operation that failed (:serialization, etc.)
103
+ # - :field [Symbol] Field name (for serialization errors)
104
+ # - :object_class [String] Class name of the object
105
+ #
106
+ # @example
107
+ # Familia.on_error do |error, ctx|
108
+ # Sentry.capture_exception(error, extra: ctx)
109
+ # end
110
+ #
111
+ def on_error(&block)
112
+ @hooks[:error] << block
113
+ end
114
+
115
+ # Notify all registered command hooks.
116
+ # @api private
117
+ def notify_command(cmd, duration, context = {})
118
+ @hooks[:command].each do |hook|
119
+ hook.call(cmd, duration, context)
120
+ rescue => e
121
+ Familia.error("Instrumentation hook failed", error: e.message, hook_type: :command)
122
+ end
123
+ end
124
+
125
+ # Notify all registered pipeline hooks.
126
+ # @api private
127
+ def notify_pipeline(command_count, duration, context = {})
128
+ @hooks[:pipeline].each do |hook|
129
+ hook.call(command_count, duration, context)
130
+ rescue => e
131
+ Familia.error("Instrumentation hook failed", error: e.message, hook_type: :pipeline)
132
+ end
133
+ end
134
+
135
+ # Notify all registered lifecycle hooks.
136
+ # @api private
137
+ def notify_lifecycle(event, instance, context = {})
138
+ @hooks[:lifecycle].each do |hook|
139
+ hook.call(event, instance, context)
140
+ rescue => e
141
+ Familia.error("Instrumentation hook failed", error: e.message, hook_type: :lifecycle)
142
+ end
143
+ end
144
+
145
+ # Notify all registered error hooks.
146
+ # @api private
147
+ def notify_error(error, context = {})
148
+ @hooks[:error].each do |hook|
149
+ hook.call(error, context)
150
+ rescue => e
151
+ # Don't recurse on hook failures - just silently skip
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -1,4 +1,6 @@
1
1
  # lib/familia/json_serializer.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  # JsonSerializer provides a high-performance JSON interface using OJ
@@ -1,4 +1,6 @@
1
1
  # lib/familia/logging.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require 'pathname'
4
6
  require 'logger'
@@ -117,7 +119,7 @@ module Familia
117
119
  'ERROR' => 'E',
118
120
  'FATAL' => 'F',
119
121
  'UNKNOWN' => 'U',
120
- 'ANY' => 'T' # ANY is Logger's label for severity < 0, treat as TRACE
122
+ 'ANY' => 'T', # ANY is Logger's label for severity < 0, treat as TRACE
121
123
  }.freeze
122
124
 
123
125
  # Format a log message with severity, timestamp, and context.
@@ -173,8 +175,16 @@ module Familia
173
175
  # Familia.trace :LOAD, redis_client, "user:123", "from cache"
174
176
  #
175
177
  module Logging
178
+ # Thread-safe mutex initialization when module is extended
179
+ def self.extended(base)
180
+ base.instance_variable_set(:@logger_mutex, Mutex.new)
181
+ end
182
+
176
183
  # Get the logger instance, initializing with defaults if not yet set
177
184
  #
185
+ # Thread-safe lazy initialization using double-checked locking to ensure
186
+ # only a single logger instance is created even under concurrent logging calls.
187
+ #
178
188
  # @return [FamiliaLogger] the logger instance
179
189
  #
180
190
  # @example Set a custom logger
@@ -184,10 +194,18 @@ module Familia
184
194
  # Familia.logger.info "Connection established"
185
195
  #
186
196
  def logger
187
- @logger ||= FamiliaLogger.new($stderr).tap do |log|
188
- log.progname = name
189
- log.formatter = LogFormatter.new
197
+ # Fast path: return existing logger if already initialized
198
+ return @logger if @logger
199
+
200
+ # Slow path: thread-safe initialization
201
+ @logger_mutex.synchronize do
202
+ @logger ||= FamiliaLogger.new($stderr).tap do |log|
203
+ log.progname = name
204
+ log.formatter = LogFormatter.new
205
+ end
190
206
  end
207
+
208
+ @logger
191
209
  end
192
210
 
193
211
  # Set a custom logger instance.
@@ -195,6 +213,9 @@ module Familia
195
213
  # Allows replacing the default FamiliaLogger with any Logger-compatible
196
214
  # object. Useful for integrating with application logging frameworks.
197
215
  #
216
+ # Automatically synchronizes the logger to DatabaseLogger if it's loaded,
217
+ # ensuring consistent logging across Familia's middleware stack.
218
+ #
198
219
  # @param new_logger [Logger] The logger to use
199
220
  # @return [Logger] The logger that was set
200
221
  #
@@ -207,20 +228,14 @@ module Familia
207
228
  # end
208
229
  #
209
230
  def logger=(new_logger)
210
- @logger = new_logger
231
+ @logger_mutex.synchronize do
232
+ @logger = new_logger
233
+ # Auto-sync to DatabaseLogger if loaded (inside mutex for atomicity)
234
+ DatabaseLogger.logger = new_logger if defined?(DatabaseLogger)
235
+ end
236
+ @logger
211
237
  end
212
238
 
213
- # Log an informational message.
214
- #
215
- # @param msg [String] The message to log
216
- # @return [true]
217
- #
218
- # @example
219
- # Familia.info "Redis connection established"
220
- #
221
- def info(msg)
222
- logger.info(msg)
223
- end
224
239
 
225
240
  # Log a warning message.
226
241
  #
@@ -234,34 +249,58 @@ module Familia
234
249
  logger.warn(msg)
235
250
  end
236
251
 
237
- # Log a debug message (only when Familia.debug? is true).
252
+ # Log a debug message with optional structured context.
238
253
  #
239
- # Short for "log debug". Only outputs when FAMILIA_DEBUG environment
240
- # variable is set to '1' or 'true'.
254
+ # Only outputs when FAMILIA_DEBUG environment variable is enabled.
255
+ # Supports both simple string messages and structured logging with
256
+ # keyword context for operational observability.
241
257
  #
242
- # @param msg [String] The message to log
258
+ # @param message [String, nil] The message to log
259
+ # @param context [Hash] Structured context (key-value pairs)
243
260
  # @return [true, nil] Returns true if logged, nil if debug disabled
244
261
  #
245
- # @example
246
- # Familia.ld "Cache lookup for user:123"
247
- # # Only outputs when FAMILIA_DEBUG=true
262
+ # @example Simple message
263
+ # Familia.debug "Cache lookup for user:123"
264
+ #
265
+ # @example Structured context
266
+ # Familia.debug "Horreum saved", class: "User", identifier: "user_123", duration: 1234
267
+ # # => "Horreum saved class=User identifier=user_123 duration=1234"
248
268
  #
249
- def ld(msg)
250
- logger.debug(msg) if Familia.debug?
269
+ def debug(message = nil, **context)
270
+ return unless Familia.debug?
271
+ logger.debug(format_log(message, context))
251
272
  end
252
273
 
253
- # Log an error message.
274
+ # Log an informational message with optional structured context.
254
275
  #
255
- # Short for "log error".
276
+ # @param message [String, nil] The message to log
277
+ # @param context [Hash] Structured context (key-value pairs)
278
+ # @return [true]
256
279
  #
257
- # @param msg [String] The message to log
280
+ # @example Simple message
281
+ # Familia.info "Connection pool initialized"
282
+ #
283
+ # @example Structured context
284
+ # Familia.info "Pipeline executed", commands: 5, duration: 2340
285
+ #
286
+ def info(message = nil, **context)
287
+ logger.info(format_log(message, context))
288
+ end
289
+
290
+ # Log an error message with optional structured context.
291
+ #
292
+ # @param message [String, nil] The message to log
293
+ # @param context [Hash] Structured context (key-value pairs)
258
294
  # @return [true]
259
295
  #
260
- # @example
261
- # Familia.le "Failed to deserialize value: #{e.message}"
296
+ # @example Simple message
297
+ # Familia.error "Failed to deserialize value"
298
+ #
299
+ # @example Structured context
300
+ # Familia.error "Serialization failed", field: :email, error: e.message, class: "User"
262
301
  #
263
- def le(msg)
264
- logger.error(msg)
302
+ def error(message = nil, **context)
303
+ logger.error(format_log(message, context))
265
304
  end
266
305
 
267
306
  # Logs a structured trace message for debugging Familia operations.
@@ -293,6 +332,27 @@ module Familia
293
332
 
294
333
  private
295
334
 
335
+ # Format a log message with optional structured context.
336
+ #
337
+ # Combines a message string with key-value context into a single
338
+ # log line. Empty context returns the message unchanged.
339
+ #
340
+ # @param message [String, nil] The message to log
341
+ # @param context [Hash] Structured context (key-value pairs)
342
+ # @return [String] Formatted log message
343
+ # @api private
344
+ #
345
+ # @example
346
+ # format_log("User saved", id: 123, duration: 1.5)
347
+ # # => "User saved id=123 duration=1.5"
348
+ #
349
+ def format_log(message, context)
350
+ return message if context.empty?
351
+ parts = [message]
352
+ parts << context.map { |k, v| "#{k}=#{v}" }.join(' ')
353
+ parts.compact.join(' ')
354
+ end
355
+
296
356
  # Check if trace logging is enabled via FAMILIA_TRACE environment variable.
297
357
  #
298
358
  # Trace logging is enabled when FAMILIA_TRACE is set to '1', 'true',
@@ -1,4 +1,6 @@
1
1
  # lib/familia/refinements/dear_json.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require 'familia/json_serializer'
4
6
 
@@ -1,4 +1,6 @@
1
1
  # lib/familia/refinements/stylize_words.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Refinements
@@ -16,20 +18,6 @@ module Familia
16
18
  .downcase
17
19
  end
18
20
 
19
- # Convert from plural to singular form using basic English rules
20
- def singularize
21
- word = to_s
22
- if word.end_with?('ies')
23
- "#{word[0..-4]}y"
24
- elsif word.end_with?('es') && word.length > 3
25
- word[0..-3]
26
- elsif word.end_with?('s') && word.length > 1
27
- word[0..-2]
28
- else
29
- word
30
- end
31
- end
32
-
33
21
  # Convert to camelCase
34
22
  def camelize
35
23
  _ize(:lower)
@@ -1,4 +1,6 @@
1
1
  # lib/familia/refinements/time_literals.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Refinements
@@ -1,4 +1,6 @@
1
1
  # lib/familia/refinements.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require_relative 'refinements/dear_json'
4
6
  require_relative 'refinements/stylize_words'
@@ -1,4 +1,6 @@
1
1
  # lib/familia/secure_identifier.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require 'securerandom'
4
6
 
@@ -118,6 +120,10 @@ module Familia
118
120
 
119
121
  # @private
120
122
  #
123
+ # Thread-safe lazy initialization using Concurrent::Map to ensure
124
+ # atomic cache population and consistent calculations across all
125
+ # concurrent ID generation requests.
126
+ #
121
127
  # @param bits [Integer] The number of bits of entropy.
122
128
  # @param base [Integer] The numeric base (2-36).
123
129
  # @return [Integer] The minimum string length required.
@@ -130,8 +136,10 @@ module Familia
130
136
  }.freeze
131
137
  return hex_lengths[bits] if base == 16 && hex_lengths.key?(bits)
132
138
 
133
- @min_length_for_bits_cache ||= {}
134
- @min_length_for_bits_cache[[bits, base]] ||= (bits * Math.log(2) / Math.log(base)).ceil
139
+ @min_length_for_bits_cache ||= Concurrent::Map.new
140
+ @min_length_for_bits_cache.fetch_or_store([bits, base]) do
141
+ (bits * Math.log(2) / Math.log(base)).ceil
142
+ end
135
143
  end
136
144
  end
137
145
  end
@@ -1,4 +1,6 @@
1
1
  # lib/familia/settings.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # Familia
4
6
  #
@@ -0,0 +1,166 @@
1
+ # lib/familia/thread_safety/instrumented_mutex.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require_relative 'monitor'
6
+
7
+ module Familia
8
+ module ThreadSafety
9
+ # A Mutex wrapper that automatically reports contention metrics
10
+ #
11
+ # This class wraps Ruby's standard Mutex to provide automatic
12
+ # instrumentation of lock contention and wait times.
13
+ #
14
+ # @example Basic usage
15
+ # mutex = Familia::ThreadSafety::InstrumentedMutex.new('connection_chain')
16
+ # mutex.synchronize { # critical section }
17
+ #
18
+ # @example With monitoring
19
+ # Familia::ThreadSafety::Monitor.start!
20
+ # mutex = Familia::ThreadSafety::InstrumentedMutex.new('field_registration')
21
+ # mutex.synchronize { # automatically tracked }
22
+ class InstrumentedMutex
23
+ attr_reader :name, :mutex
24
+
25
+ # Create a new instrumented mutex
26
+ #
27
+ # @param name [String, Symbol] Identifier for this mutex in monitoring
28
+ # @param monitor [Monitor, nil] Monitor instance to use (defaults to singleton)
29
+ def initialize(name, monitor = nil)
30
+ @name = name.to_s
31
+ @mutex = ::Mutex.new
32
+ @monitor = monitor || Monitor.instance
33
+ @lock_count = Concurrent::AtomicFixnum.new(0)
34
+ @contention_count = Concurrent::AtomicFixnum.new(0)
35
+ end
36
+
37
+ # Synchronize with automatic monitoring
38
+ #
39
+ # @yield Block to execute while holding the lock
40
+ # @return Result of the block
41
+ def synchronize
42
+ return yield unless @monitor.enabled
43
+
44
+ acquired = false
45
+ wait_start = Familia.now_in_μs
46
+
47
+ # Try non-blocking acquisition first to detect contention
48
+ if @mutex.try_lock
49
+ acquired = true
50
+ wait_time = 0
51
+ @lock_count.increment
52
+ else
53
+ # Contention detected
54
+ @contention_count.increment
55
+ @monitor.record_contention(@name)
56
+
57
+ # Now do blocking acquisition
58
+ @mutex.lock
59
+ acquired = true
60
+ wait_end = Familia.now_in_μs
61
+ wait_time_μs = wait_end - wait_start
62
+
63
+ @lock_count.increment
64
+ @monitor.record_wait_time(@name, wait_time_μs)
65
+
66
+ if wait_time_μs > 10_000 # Log if waited more than 10ms (10,000μs)
67
+ Familia.trace(:MUTEX_WAIT, nil, "Waited #{(wait_time_μs / 1000.0).round(2)}ms for #{@name}")
68
+ end
69
+ end
70
+
71
+ yield
72
+ ensure
73
+ @mutex.unlock if acquired
74
+ end
75
+
76
+ # Acquire the lock (with monitoring)
77
+ def lock
78
+ return @mutex.lock unless @monitor.enabled
79
+
80
+ wait_start = Familia.now_in_μs
81
+
82
+ if @mutex.try_lock
83
+ @lock_count.increment
84
+ return true
85
+ end
86
+
87
+ # Contention detected
88
+ @contention_count.increment
89
+ @monitor.record_contention(@name)
90
+
91
+ result = @mutex.lock
92
+ wait_end = Familia.now_in_μs
93
+ wait_time_μs = wait_end - wait_start
94
+
95
+ @lock_count.increment
96
+ @monitor.record_wait_time(@name, wait_time_μs)
97
+
98
+ result
99
+ end
100
+
101
+ # Try to acquire the lock without blocking
102
+ def try_lock
103
+ result = @mutex.try_lock
104
+ @lock_count.increment if result
105
+ result
106
+ end
107
+
108
+ # Release the lock
109
+ def unlock
110
+ @mutex.unlock
111
+ end
112
+
113
+ # Check if locked by current thread
114
+ def locked?
115
+ @mutex.locked?
116
+ end
117
+
118
+ # Check if owned by current thread
119
+ def owned?
120
+ @mutex.owned?
121
+ end
122
+
123
+ # Sleep and release the lock temporarily
124
+ def sleep(timeout = nil)
125
+ @mutex.sleep(timeout)
126
+ end
127
+
128
+ # Get statistics for this mutex
129
+ def stats
130
+ {
131
+ name: @name,
132
+ lock_count: @lock_count.value,
133
+ contention_count: @contention_count.value,
134
+ contention_rate: contention_rate
135
+ }
136
+ end
137
+
138
+ # Calculate contention rate (0.0 to 1.0)
139
+ def contention_rate
140
+ total = @lock_count.value
141
+ return 0.0 if total == 0
142
+
143
+ @contention_count.value.to_f / total
144
+ end
145
+
146
+ # Create a double-checked locking helper
147
+ #
148
+ # @param check [Proc] Condition to check
149
+ # @param init [Proc] Initialization to perform if check fails
150
+ # @return Result of check or init
151
+ def double_checked_locking(check, init)
152
+ # Fast path - check without lock
153
+ value = check.call
154
+ return value if value
155
+
156
+ # Slow path - check again with lock
157
+ synchronize do
158
+ value = check.call
159
+ return value if value
160
+
161
+ init.call
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end