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,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
@@ -0,0 +1,328 @@
1
+ # lib/familia/thread_safety/monitor.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require 'concurrent-ruby'
6
+
7
+ module Familia
8
+ module ThreadSafety
9
+ # Thread safety monitoring for production observability
10
+ #
11
+ # Tracks mutex contention, race conditions, and synchronization metrics
12
+ # to provide insights into thread safety behavior in production.
13
+ #
14
+ # @example Basic usage
15
+ # Familia::ThreadSafety::Monitor.start!
16
+ # # ... application runs ...
17
+ # report = Familia::ThreadSafety::Monitor.report
18
+ # puts report[:summary]
19
+ #
20
+ # @example Custom instrumentation
21
+ # Familia::ThreadSafety::Monitor.record_contention('connection_chain')
22
+ # Familia::ThreadSafety::Monitor.time_critical_section('field_registration') do
23
+ # # ... critical code ...
24
+ # end
25
+ class Monitor
26
+ class << self
27
+ def instance
28
+ @instance ||= new
29
+ end
30
+
31
+ # Delegate all methods to singleton instance
32
+ def method_missing(method, *args, &block)
33
+ if instance.respond_to?(method)
34
+ instance.send(method, *args, &block)
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ def respond_to_missing?(method, include_private = false)
41
+ instance.respond_to?(method) || super
42
+ end
43
+ end
44
+
45
+ attr_reader :enabled, :started_at
46
+
47
+ def initialize
48
+ @enabled = false
49
+ @started_at = nil
50
+ @mutex_contentions = Concurrent::AtomicFixnum.new(0)
51
+ @race_detections = Concurrent::AtomicFixnum.new(0)
52
+ @critical_sections = Concurrent::AtomicFixnum.new(0)
53
+ @deadlock_checks = Concurrent::AtomicFixnum.new(0)
54
+
55
+ # Track contention points with counts
56
+ @contention_points = Concurrent::Map.new
57
+
58
+ # Track wait time aggregates for critical sections (removed @wait_times to prevent memory leak)
59
+ @wait_time_totals = Concurrent::Map.new
60
+ @wait_time_counts = Concurrent::Map.new
61
+
62
+ # Track thread-local state for nested monitoring
63
+ @thread_state = Concurrent::Map.new
64
+
65
+ # Track concurrent operation counts
66
+ @concurrent_operations = Concurrent::Map.new
67
+
68
+ # Performance metrics
69
+ @section_timings = Concurrent::Map.new
70
+ @section_counts = Concurrent::Map.new
71
+ end
72
+
73
+ # Start monitoring
74
+ def start!
75
+ @enabled = true
76
+ @started_at = Time.now
77
+ reset_metrics
78
+ Familia.info("[ThreadSafety] Monitoring started")
79
+ true
80
+ end
81
+
82
+ # Stop monitoring
83
+ def stop!
84
+ @enabled = false
85
+ duration = @started_at ? Time.now - @started_at : 0
86
+ Familia.info("[ThreadSafety] Monitoring stopped after #{duration.round(2)}s")
87
+ @started_at = nil
88
+ true
89
+ end
90
+
91
+ # Reset all metrics
92
+ def reset_metrics
93
+ @mutex_contentions.value = 0
94
+ @race_detections.value = 0
95
+ @critical_sections.value = 0
96
+ @deadlock_checks.value = 0
97
+ @contention_points.clear
98
+ # @wait_times.clear - removed to prevent memory leak
99
+ @wait_time_totals.clear
100
+ @wait_time_counts.clear
101
+ @thread_state.clear
102
+ @concurrent_operations.clear
103
+ @section_timings.clear
104
+ @section_counts.clear
105
+ end
106
+
107
+ # Record a mutex contention event
108
+ def record_contention(location, wait_time = nil)
109
+ return unless @enabled
110
+
111
+ @mutex_contentions.increment
112
+ @contention_points[location] = @contention_points.fetch(location, 0) + 1
113
+
114
+ if wait_time
115
+ record_wait_time(location, wait_time)
116
+ end
117
+
118
+ Familia.trace(:THREAD_CONTENTION, nil, "Contention at #{location} (wait: #{wait_time&.round(4)}s)")
119
+ end
120
+
121
+ # Record wait time for a location
122
+ def record_wait_time(location, wait_time)
123
+ # Note: @wait_times was removed to prevent memory leak from unbounded array growth
124
+ # We only need the aggregated totals and counts for calculations
125
+ @wait_time_totals[location] = @wait_time_totals.fetch(location, 0.0) + wait_time
126
+ @wait_time_counts[location] = @wait_time_counts.fetch(location, 0) + 1
127
+ end
128
+
129
+ # Record a potential race condition detection
130
+ def record_race_condition(location, details = nil)
131
+ return unless @enabled
132
+
133
+ @race_detections.increment
134
+ msg = "Potential race condition at #{location}"
135
+ msg += ": #{details}" if details
136
+ Familia.warn("[ThreadSafety] #{msg}")
137
+ end
138
+
139
+ # Time a critical section with contention tracking
140
+ def time_critical_section(name)
141
+ return yield unless @enabled
142
+
143
+ thread_id = Thread.current.object_id
144
+ start_time = Familia.now_in_μs
145
+
146
+ # Check for concurrent execution
147
+ concurrent_count = @concurrent_operations[name] = @concurrent_operations.fetch(name, 0) + 1
148
+ if concurrent_count > 1
149
+ record_contention(name)
150
+ end
151
+
152
+ @critical_sections.increment
153
+
154
+ begin
155
+ result = yield
156
+ ensure
157
+ end_time = Familia.now_in_μs
158
+ duration_μs = end_time - start_time
159
+
160
+ # Record timing in microseconds
161
+ @section_timings[name] = @section_timings.fetch(name, 0) + duration_μs
162
+ @section_counts[name] = @section_counts.fetch(name, 0) + 1
163
+
164
+ # Decrement concurrent count
165
+ @concurrent_operations[name] = @concurrent_operations.fetch(name, 1) - 1
166
+
167
+ if duration_μs > 100_000 # Log slow critical sections (> 100ms = 100,000μs)
168
+ Familia.warn("[ThreadSafety] Slow critical section '#{name}': #{(duration_μs / 1000.0).round(2)}ms")
169
+ end
170
+ end
171
+
172
+ result
173
+ end
174
+
175
+ # NOTE: monitor_mutex method was removed as it was unused and had flawed
176
+ # exception handling that could lead to deadlocks. The InstrumentedMutex
177
+ # class should be used instead for mutex monitoring.
178
+
179
+ # Check for potential deadlocks
180
+ def check_deadlock
181
+ return unless @enabled
182
+
183
+ @deadlock_checks.increment
184
+
185
+ # This is a simple check - in production you might want more sophisticated detection
186
+ thread_count = Thread.list.count
187
+ if thread_count > 100
188
+ Familia.warn("[ThreadSafety] High thread count: #{thread_count}")
189
+ end
190
+
191
+ # Check for threads waiting on mutexes (simplified)
192
+ waiting_threads = Thread.list.select { |t| t.status == "sleep" }
193
+ if waiting_threads.size > thread_count * 0.8
194
+ Familia.warn("[ThreadSafety] Potential deadlock: #{waiting_threads.size}/#{thread_count} threads sleeping")
195
+ end
196
+ end
197
+
198
+ # Generate a comprehensive report
199
+ def report
200
+ return { enabled: false, message: "Monitoring not enabled" } unless @started_at
201
+
202
+ duration = Time.now - @started_at
203
+
204
+ # Calculate hot spots
205
+ hot_spots = []
206
+ @contention_points.each_pair do |location, count|
207
+ hot_spots << [location, count]
208
+ end
209
+ hot_spots = hot_spots
210
+ .sort_by { |_, count| -count }
211
+ .first(10)
212
+ .map { |location, count|
213
+ avg_wait_μs = if @wait_time_counts[location] && @wait_time_counts[location] > 0
214
+ (@wait_time_totals[location] / @wait_time_counts[location]).round(0)
215
+ else
216
+ 0
217
+ end
218
+ {
219
+ location: location,
220
+ contentions: count,
221
+ avg_wait_μs: avg_wait_μs
222
+ }
223
+ }
224
+
225
+ # Calculate critical section performance
226
+ section_performance = []
227
+ @section_counts.each_pair do |name, count|
228
+ avg_time_μs = (@section_timings[name] / count).round(0)
229
+ section_performance << {
230
+ section: name,
231
+ calls: count,
232
+ avg_time_μs: avg_time_μs,
233
+ total_time_μs: @section_timings[name]
234
+ }
235
+ end
236
+ section_performance.sort_by! { |s| -s[:total_time_μs] }
237
+
238
+ {
239
+ summary: {
240
+ monitoring_duration_s: duration.round(2),
241
+ mutex_contentions: @mutex_contentions.value,
242
+ race_detections: @race_detections.value,
243
+ critical_sections: @critical_sections.value,
244
+ deadlock_checks: @deadlock_checks.value
245
+ },
246
+ hot_spots: hot_spots,
247
+ section_performance: section_performance,
248
+ health: calculate_health_score,
249
+ recommendations: generate_recommendations(hot_spots)
250
+ }
251
+ end
252
+
253
+ # Calculate a health score (0-100)
254
+ def calculate_health_score
255
+ return 100 unless @started_at
256
+
257
+ duration = Time.now - @started_at
258
+ return 100 if duration < 60 # Need at least 1 minute of data
259
+
260
+ contentions_per_hour = (@mutex_contentions.value / duration) * 3600
261
+ races_per_hour = (@race_detections.value / duration) * 3600
262
+
263
+ score = 100
264
+ score -= [contentions_per_hour / 10.0, 30].min # -3 points per 100 contentions/hour, max -30
265
+ score -= [races_per_hour * 10, 50].min # -10 points per race/hour, max -50
266
+
267
+ [score, 0].max.round
268
+ end
269
+
270
+ # Generate recommendations based on metrics
271
+ def generate_recommendations(hot_spots)
272
+ recommendations = []
273
+
274
+ if @race_detections.value > 0
275
+ recommendations << {
276
+ severity: 'critical',
277
+ message: "#{@race_detections.value} potential race conditions detected - investigate immediately"
278
+ }
279
+ end
280
+
281
+ if hot_spots.any? { |h| h[:contentions] > 100 }
282
+ high_contention = hot_spots.select { |h| h[:contentions] > 100 }
283
+ locations = high_contention.map { |h| h[:location] }.join(', ')
284
+ recommendations << {
285
+ severity: 'warning',
286
+ message: "High contention detected at: #{locations}"
287
+ }
288
+ end
289
+
290
+ if hot_spots.any? { |h| h[:avg_wait_μs] > 100_000 } # > 100ms in microseconds
291
+ slow_spots = hot_spots.select { |h| h[:avg_wait_μs] > 100_000 }
292
+ recommendations << {
293
+ severity: 'warning',
294
+ message: "Long wait times at: #{slow_spots.map { |h| "#{h[:location]} (#{(h[:avg_wait_μs] / 1000.0).round(1)}ms)" }.join(', ')}"
295
+ }
296
+ end
297
+
298
+ if @deadlock_checks.value > 0 && Thread.list.count > 50
299
+ recommendations << {
300
+ severity: 'info',
301
+ message: "Consider connection pooling - high thread count detected"
302
+ }
303
+ end
304
+
305
+ recommendations
306
+ end
307
+
308
+ # Export metrics in a format suitable for APM tools
309
+ def export_metrics
310
+ {
311
+ 'familia.thread_safety.mutex_contentions' => @mutex_contentions.value,
312
+ 'familia.thread_safety.race_detections' => @race_detections.value,
313
+ 'familia.thread_safety.critical_sections' => @critical_sections.value,
314
+ 'familia.thread_safety.deadlock_checks' => @deadlock_checks.value,
315
+ 'familia.thread_safety.health_score' => calculate_health_score
316
+ }
317
+ end
318
+
319
+ # Hook for APM integration
320
+ def apm_transaction(name, &block)
321
+ return yield unless @enabled
322
+
323
+ # This is where you'd integrate with NewRelic, DataDog, etc.
324
+ time_critical_section(name, &block)
325
+ end
326
+ end
327
+ end
328
+ end
data/lib/familia/utils.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  # lib/familia/utils.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  # Family-related utility methods
@@ -42,6 +44,17 @@ module Familia
42
44
  current_time.utc.to_f
43
45
  end
44
46
 
47
+ # Returns the current time in microseconds.
48
+ # This is used to measure the duration of Database commands.
49
+ #
50
+ # Alias: now_in_microseconds
51
+ #
52
+ # @return [Integer] The current time in microseconds.
53
+ def now_in_μs
54
+ Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
55
+ end
56
+ alias now_in_microseconds now_in_μs
57
+
45
58
  # A quantized timestamp
46
59
  #
47
60
  # @param quantum [Integer] The time quantum in seconds (default: 10 minutes).
@@ -1,4 +1,6 @@
1
1
  # lib/familia/verifiable_identifier.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require 'openssl'
4
6
  require_relative 'secure_identifier'
@@ -33,7 +35,7 @@ module Familia
33
35
  # $ openssl rand -hex 32
34
36
  # > cafef00dcafef00dcafef00dcafef00dcafef00dcafef00d
35
37
  #
36
- # 2. UnsortedSet it as an environment variable in your production environment:
38
+ # 2. Set it as an environment variable in your production environment:
37
39
  # export VERIFIABLE_ID_HMAC_SECRET="cafef00dcafef00dcafef00dcafef00dcafef00dcafef00d"
38
40
  #
39
41
  SECRET_KEY = ENV.fetch('VERIFIABLE_ID_HMAC_SECRET', 'cafef00dcafef00dcafef00dcafef00dcafef00dcafef00d')
@@ -1,6 +1,8 @@
1
1
  # lib/familia/version.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  # Version information for the Familia
5
- VERSION = '2.0.0.pre18'.freeze unless defined?(Familia::VERSION)
7
+ VERSION = '2.0.0.pre21'.freeze unless defined?(Familia::VERSION)
6
8
  end
data/lib/familia.rb CHANGED
@@ -1,9 +1,12 @@
1
1
  # lib/familia.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require 'oj'
4
6
  require 'redis'
5
7
  require 'uri/valkey'
6
8
  require 'connection_pool'
9
+ require 'concurrent-ruby'
7
10
 
8
11
  # OJ configuration is handled internally by Familia::JsonSerializer
9
12
 
@@ -11,6 +14,8 @@ require_relative 'multi_result'
11
14
  require_relative 'familia/refinements'
12
15
  require_relative 'familia/errors'
13
16
  require_relative 'familia/version'
17
+ require_relative 'familia/thread_safety/monitor'
18
+ require_relative 'familia/thread_safety/instrumented_mutex'
14
19
 
15
20
  # Familia - A family warehouse for Valkey/Redis
16
21
  #
@@ -39,9 +44,30 @@ module Familia
39
44
  using Refinements::StylizeWords
40
45
 
41
46
  class << self
42
- attr_accessor :debug
47
+ attr_writer :debug
43
48
  attr_reader :members
44
49
 
50
+ # Thread safety monitoring controls
51
+ def thread_safety_monitor
52
+ ThreadSafety::Monitor.instance
53
+ end
54
+
55
+ def start_monitoring!
56
+ thread_safety_monitor.start!
57
+ end
58
+
59
+ def stop_monitoring!
60
+ thread_safety_monitor.stop!
61
+ end
62
+
63
+ def thread_safety_report
64
+ thread_safety_monitor.report
65
+ end
66
+
67
+ def thread_safety_metrics
68
+ thread_safety_monitor.export_metrics
69
+ end
70
+
45
71
  def included(member)
46
72
  raise Problem, "#{member} should subclass Familia::Horreum"
47
73
  end
@@ -87,7 +113,7 @@ module Familia
87
113
  # @param klass [Class] The class to remove from members
88
114
  # @return [Class, nil] The removed class or nil if not found
89
115
  def unload_member(klass)
90
- Familia.ld "[unload_member] Removing #{klass} from members"
116
+ Familia.debug "[unload_member] Removing #{klass} from members"
91
117
  @members.delete(klass)
92
118
  end
93
119
 
@@ -97,7 +123,7 @@ module Familia
97
123
  # @return [Array<Class>] The removed anonymous classes
98
124
  def clear_anonymous_members
99
125
  anonymous_classes = @members.select { |m| m.name.nil? }
100
- Familia.ld "[clear_anonymous_members] Removing #{anonymous_classes.size} anonymous classes"
126
+ Familia.debug "[clear_anonymous_members] Removing #{anonymous_classes.size} anonymous classes"
101
127
  @members.reject! { |m| m.name.nil? }
102
128
  anonymous_classes
103
129
  end
@@ -128,7 +154,7 @@ module Familia
128
154
  # Familia.member_by_config_name(:nonexistent) # => nil
129
155
  #
130
156
  def member_by_config_name(config_name)
131
- Familia.ld "[member_by_config_name] #{members.map(&:config_name)} #{config_name}"
157
+ Familia.debug "[member_by_config_name] #{members.map(&:config_name)} #{config_name}"
132
158
 
133
159
  members.find { |m| m.config_name.to_s.eql?(config_name.to_s) }
134
160
  end
@@ -149,6 +175,7 @@ module Familia
149
175
  extend Utils
150
176
  end
151
177
 
178
+ require_relative 'familia/instrumentation'
152
179
  require_relative 'familia/base'
153
180
  require_relative 'familia/features'
154
181
  require_relative 'familia/data_type'