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
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
- # pool_siege.rb
2
+ # try/support/prototypes/pooling/pool_siege.rb
3
3
  #
4
+ # frozen_string_literal: true
5
+
6
+ # pool_siege.rb
4
7
  # Simple Connection Pool Load Tester - Like siege, but for Database connection pools
5
- #
6
8
  # Usage:
7
9
  # ruby pool_siege.rb -t 20 -p 5 -o 100 # 20 threads, 5 pool size, 100 ops each
8
10
  # ruby pool_siege.rb --stress # Find breaking point
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
- # try/prototypes/run_stress_tests.rb
2
+ # try/support/prototypes/pooling/run_stress_tests.rb
3
3
  #
4
+ # frozen_string_literal: true
5
+
6
+ # try/prototypes/run_stress_tests.rb
4
7
  # Main Test Runner for Connection Pool Stress Tests
5
- #
6
8
  # This script orchestrates comprehensive stress testing of the connection pool
7
9
  # implementation across different scenarios, threading models, and configurations.
8
10
  # It generates detailed reports and comparisons to identify bottlenecks and
@@ -0,0 +1,496 @@
1
+ # Thread Safety Test Suite
2
+
3
+ ## Purpose
4
+
5
+ This directory contains targeted tests that expose threading vulnerabilities in Familia's codebase. These tests were created following a systematic analysis that identified critical race conditions in production code that currently have no synchronization protection.
6
+
7
+ **Key Insight**: These tests are designed to **fail or expose inconsistencies** because the underlying code lacks proper synchronization. As of the latest updates, test assertions have been strengthened to properly verify thread safety invariants (singleton properties, consistency, etc.) rather than just checking thread completion.
8
+
9
+ **Current Status**: As of 2025-10-21, test suite has **61 passing / 4 failing** tests. The 4 failures properly detect real race conditions and bugs that need fixing in production code. This honest failure rate is intentional and expected - it shows exactly where work is needed.
10
+
11
+ ## The Thread Safety Problem
12
+
13
+ Familia uses several patterns that are inherently unsafe under concurrent access:
14
+
15
+ ### 1. Lazy Initialization Without Synchronization
16
+ ```ruby
17
+ # This pattern appears throughout the codebase
18
+ def connection_chain
19
+ @connection_chain ||= build_connection_chain # RACE CONDITION
20
+ end
21
+ ```
22
+
23
+ **The Race**: Two threads can both see `@connection_chain` as nil, both call `build_connection_chain`, and create duplicate objects. The second write wins, but the first object is leaked.
24
+
25
+ **Where it appears**:
26
+ - Module-level connection chain
27
+ - Class-level connection chain (per Horreum subclass)
28
+ - Feature registry collections
29
+ - Field definition collections
30
+ - Various caches (encryption manager, SecureIdentifier, logger)
31
+
32
+ ### 2. Check-Then-Act Races
33
+ ```ruby
34
+ # From middleware registration
35
+ if !@logger_registered
36
+ RedisClient.register(DatabaseLogger) # RACE CONDITION
37
+ @logger_registered = true
38
+ end
39
+ ```
40
+
41
+ **The Race**: Thread A checks the flag (false), gets interrupted. Thread B checks (false), registers middleware, sets flag. Thread A resumes and registers again - now you have duplicate middleware in the chain.
42
+
43
+ ### 3. Non-Atomic Updates
44
+ ```ruby
45
+ # From middleware versioning
46
+ def increment_middleware_version!
47
+ @middleware_version += 1 # RACE CONDITION: read-modify-write
48
+ end
49
+ ```
50
+
51
+ **The Race**: Classic lost update problem. With 100 concurrent increments, you might get 97 instead of 100 due to interleaved reads.
52
+
53
+ ### 4. Unprotected Shared State
54
+ ```ruby
55
+ # Module configuration
56
+ @uri = URI.parse('redis://...')
57
+ @prefix = nil
58
+ @delim = ':'
59
+ # Multiple threads can read/write these without coordination
60
+ ```
61
+
62
+ **The Race**: Thread A reads `@delim` as ":", Thread B changes it to "::", Thread A uses the old value - inconsistent key generation.
63
+
64
+ ## Testing Strategy
65
+
66
+ ### What We're Testing For
67
+
68
+ 1. **Singleton Violations**: Lazy initialization creating multiple instances
69
+ 2. **Lost Updates**: Concurrent increments not all counted
70
+ 3. **State Inconsistency**: Reads getting partially-updated values
71
+ 4. **Duplicate Operations**: Check-then-act races causing double-execution
72
+ 5. **Isolation Failures**: Fiber-local storage leaking between fibers
73
+
74
+ ### How We Test
75
+
76
+ #### CyclicBarrier for Maximum Contention
77
+ We deliberately synchronize thread start to maximize the chance of exposing races:
78
+
79
+ ```ruby
80
+ barrier = Concurrent::CyclicBarrier.new(50)
81
+ threads = 50.times.map do
82
+ Thread.new do
83
+ barrier.wait # All 50 threads pause here
84
+ # Now they all execute the racy code simultaneously
85
+ @connection_chain ||= build_connection_chain
86
+ end
87
+ end
88
+ ```
89
+
90
+ This isn't "production realistic" - it's **deliberately adversarial** to expose bugs that might only happen 1 in 1000 times in production.
91
+
92
+ #### Concurrent::Array for Safe Result Collection
93
+ Standard Ruby arrays aren't thread-safe. We use `Concurrent::Array` to collect results without adding our own bugs:
94
+
95
+ ```ruby
96
+ results = Concurrent::Array.new # Thread-safe
97
+ threads.each { |t| results << t.value }
98
+ ```
99
+
100
+ #### Multi-Property Assertion Pattern
101
+ Test multiple invariants together to catch different types of corruption:
102
+
103
+ ```ruby
104
+ # Test array corruption, correctness, and completeness
105
+ [results.any?(nil), results.all? { |r| r == 'PONG' }, results.size]
106
+ #=> [false, true, 50]
107
+ ```
108
+
109
+ This pattern catches:
110
+ - **Array corruption**: `results.any?(nil)` detects concurrent modification bugs
111
+ - **Correctness**: `results.all? { ... }` verifies logical correctness
112
+ - **Completeness**: `results.size` ensures no lost operations
113
+
114
+ #### CountDownLatch for Timeout Protection
115
+ Tests should never hang forever if there's a deadlock:
116
+
117
+ ```ruby
118
+ latch = Concurrent::CountDownLatch.new(10)
119
+ # ... spawn threads ...
120
+ latch.wait(5) # Fail fast if deadlock occurs
121
+ ```
122
+
123
+ ### What Success Looks Like
124
+
125
+ **Before Fix** (current state):
126
+ ```ruby
127
+ ## Concurrent connection chain initialization
128
+ # ...50 threads all call Familia.dbclient...
129
+ results.uniq.size
130
+ #=> 3 # FAIL: Created 3 different chains (race condition)
131
+ ```
132
+
133
+ **After Fix** (with Mutex):
134
+ ```ruby
135
+ ## Concurrent connection chain initialization
136
+ # ...50 threads all call Familia.dbclient...
137
+ results.uniq.size
138
+ #=> 1 # PASS: Only one chain created (thread-safe)
139
+ ```
140
+
141
+ ## Recent Changes (2025-10-21)
142
+
143
+ **Test Assertion Improvements**: Test assertions have been updated to properly verify thread safety rather than just checking thread completion. For example:
144
+
145
+ **Before** (weak assertion):
146
+ ```ruby
147
+ chains << chain.class.name
148
+ threads.each(&:join)
149
+ chains.size # Only checks 50 threads completed
150
+ #=> 50
151
+ ```
152
+
153
+ **After** (strong assertion):
154
+ ```ruby
155
+ chains << chain.object_id # Store actual object identity
156
+ threads.each(&:join)
157
+ chains.uniq.size # Checks singleton property
158
+ #=> 1 # Will FAIL if race creates multiple chains
159
+ ```
160
+
161
+ This makes tests properly fail when race conditions occur, providing honest feedback about thread safety status.
162
+
163
+ ## Current Test Results
164
+
165
+ **Test Suite**: 63 total tests across 9 files
166
+ **Status**: 61 passing, 2 failing (736ms runtime)
167
+ **Coverage**: ~30% of identified thread safety risks (target: ~85%)
168
+
169
+ ### Known Failures (Real Bugs Detected)
170
+
171
+ These 2 failures properly detect a real race condition in production code:
172
+
173
+ #### 1. Connection Chain Lazy Init (`connection_chain_race_try.rb:39`)
174
+
175
+ **Failure**: Expected `[false, 1]` but got `[false, 50]`
176
+ - Module-level `@connection_chain ||= build_connection_chain` lacks Mutex protection
177
+ - Creates 50 different chain instances under concurrent access (should be 1)
178
+ - **Root cause**: `lib/familia/connection.rb:95`
179
+ - **Fix needed**: Add Mutex synchronization around lazy initialization
180
+
181
+ #### 2. Connection Chain Rebuild (`connection_chain_race_try.rb:123`)
182
+
183
+ **Failure**: Expected `[false, true, 40]` but got `[false, false, 40]`
184
+ - One thread nils out `@connection_chain` while 39 others try to use it
185
+ - Some threads get errors instead of successful RedisClient instances
186
+ - **Root cause**: No synchronization protecting chain rebuilding
187
+ - **Fix needed**: Mutex around connection chain access
188
+
189
+ ### Removed Tests (Investigation Revealed Not Bugs)
190
+
191
+ These tests were removed after thorough investigation revealed they were based on incorrect assumptions:
192
+
193
+ #### 3. Sample Rate Counter Test (REMOVED - NOT A BUG)
194
+
195
+ **Original Failure**: Expected `[true, false]` (sampled ~50%), got `[false, false]` (logged all 100)
196
+
197
+ **Investigation Result**:
198
+ - `sample_rate` controls **logging output**, not **command capture**
199
+ - The `commands` array always contains all commands regardless of sample_rate
200
+ - This is intentional behavior per `database_logger.rb:153-154`
201
+ - Comment states: "Command capture is unaffected - only logger output is sampled"
202
+ - **Status**: Removed from `middleware_thread_safety_try.rb:113`
203
+
204
+ #### 4. Pipeline Command Logging Test (REMOVED - NOT A BUG)
205
+
206
+ **Original Failure**: Expected 20 pipeline commands with ' | ', got only 16
207
+
208
+ **Investigation Result**:
209
+ - Single-command pipelines exist and are valid
210
+ - `Array#join(' | ')` only adds separators BETWEEN elements
211
+ - Single-command pipeline: `['SET key val']` → no separator (nothing to separate)
212
+ - Multi-command pipeline: `['SET key1 val1', 'SET key2 val2']` → has separator
213
+ - Backend-dev investigation created 7 diagnostic testcases confirming correct behavior
214
+ - See `try/investigation/pipeline_routing/CONCLUSION.md` for full technical analysis
215
+ - **Status**: Removed from `middleware_thread_safety_try.rb:224`
216
+
217
+ ## Coverage Status
218
+
219
+ ### Working Protection (2 areas)
220
+ - ✅ **DatabaseLogger**: Uses Mutex + Concurrent::Array
221
+ - ✅ **DatabaseCommandCounter**: Uses Concurrent::AtomicFixnum
222
+
223
+ These are good examples of proper synchronization.
224
+
225
+ ### No Protection - HIGH RISK (6 areas)
226
+ - ❌ Middleware registration flags
227
+ - ❌ Middleware version counter
228
+ - ❌ Connection chain lazy init (module and class level)
229
+ - ❌ Field collections lazy init
230
+ - ❌ Module configuration state
231
+
232
+ ### No Protection - MEDIUM RISK (6 areas)
233
+ - ⚠️ Various caches (encryption, SecureIdentifier, logger)
234
+ - ⚠️ Feature registry
235
+ - ⚠️ Class inheritance copying
236
+
237
+ ### Fiber Isolation (needs verification)
238
+ - 🔍 Transaction fiber-local storage
239
+ - 🔍 Pipeline fiber-local storage
240
+
241
+ These *should* be safe by design (fiber-local variables), but we need tests to verify no leakage.
242
+
243
+ ## Known Test Issues
244
+
245
+ Most tests need minor fixes before they can run properly:
246
+
247
+ ### 1. Model Identifier Pattern
248
+ Tests currently use `identifier :object_id` which isn't valid Familia syntax.
249
+
250
+ **Need to use**:
251
+ ```ruby
252
+ class TestModel < Familia::Horreum
253
+ identifier_field :model_id
254
+ field :model_id
255
+
256
+ def init
257
+ @model_id ||= SecureRandom.hex(8)
258
+ end
259
+ end
260
+ ```
261
+
262
+ ### 2. Tryouts Expectation Format
263
+ Last line must be the value being tested, not a variable reference:
264
+
265
+ ```ruby
266
+ # Wrong
267
+ results.size
268
+ #=> results.size == 20
269
+
270
+ # Right
271
+ results.size
272
+ #=> 20
273
+ ```
274
+
275
+ ### 3. Fiber Cross-Thread Issues
276
+ Fibers can't be resumed across thread boundaries. Tests need restructuring to create fibers within threads, not across them.
277
+
278
+ ## How to Use These Tests
279
+
280
+ ### Running Tests
281
+ ```bash
282
+ # All thread safety tests
283
+ bundle exec try try/thread_safety/
284
+
285
+ # Single file
286
+ bundle exec try try/thread_safety/middleware_registration_race_try.rb
287
+
288
+ # With LLM-friendly output
289
+ FAMILIA_DEBUG=0 bundle exec try --agent try/thread_safety/
290
+ ```
291
+
292
+ ### Interpreting Results
293
+
294
+ **Test Passes**: The code has proper synchronization for this scenario.
295
+
296
+ **Test Fails with Inconsistent Results**: Race condition confirmed. Example:
297
+ ```
298
+ expected 1, got 3 # Created 3 objects instead of 1 (singleton violation)
299
+ expected 100, got 97 # Lost 3 updates (non-atomic increment)
300
+ ```
301
+
302
+ **Test Fails with Errors**: Usually means the test itself needs fixing (see Known Issues above), not necessarily a threading bug.
303
+
304
+ ### Development Workflow
305
+
306
+ 1. **Fix a test** to run properly (identifier, expectations, fiber issues)
307
+ 2. **Run the test** - expect it to fail or show inconsistencies
308
+ 3. **Add synchronization** to the production code (Mutex, AtomicFixnum, etc.)
309
+ 4. **Run test again** - should now pass consistently
310
+ 5. **Repeat** for next test
311
+
312
+ ### When Adding New Code
313
+
314
+ Ask these questions:
315
+
316
+ 1. **Is this shared mutable state?** → Needs synchronization
317
+ 2. **Is this lazy initialization?** → Use Mutex or eager initialization
318
+ 3. **Is this a counter?** → Use Concurrent::AtomicFixnum
319
+ 4. **Is this check-then-act?** → Make atomic or use Mutex
320
+ 5. **Is this a cache?** → Use ThreadSafe::Cache from concurrent-ruby
321
+
322
+ ## Thread Counts Matter
323
+
324
+ - **10 threads**: Smoke test - catches obvious issues
325
+ - **20-50 threads**: Standard - catches most race conditions
326
+ - **100 threads**: Stress test - thorough but slower
327
+ - **1000+ threads**: Overkill - flaky, slow, not recommended
328
+
329
+ We use 20-100 threads in these tests as a sweet spot between detection and speed.
330
+
331
+ ## Advanced Testing Patterns
332
+
333
+ These patterns are derived from the comprehensive middleware thread safety tests and can be applied to test any concurrent code.
334
+
335
+ ### 1. Structure Validation Pattern
336
+ Verify that concurrent operations don't corrupt data structures:
337
+
338
+ ```ruby
339
+ all_valid = results.all? do |item|
340
+ item.field1.is_a?(String) &&
341
+ item.field2.is_a?(Integer) &&
342
+ item.field3.is_a?(Float)
343
+ end
344
+
345
+ [results.size, results.any?(nil), all_valid]
346
+ #=> [50, false, true]
347
+ ```
348
+
349
+ ### 2. Concurrent Clearing Pattern
350
+ Test that clearing operations don't corrupt during active usage:
351
+
352
+ ```ruby
353
+ # 50 threads writing
354
+ writers = 50.times.map do
355
+ Thread.new do
356
+ barrier.wait
357
+ 10.times { write_operation }
358
+ end
359
+ end
360
+
361
+ # 1 thread clearing
362
+ clearer = Thread.new do
363
+ barrier.wait
364
+ 5.times { clear_operation }
365
+ end
366
+
367
+ # Array should never contain nil entries
368
+ results.any?(nil)
369
+ #=> false
370
+ ```
371
+
372
+ ### 3. Mixed Operation Types Pattern
373
+ Test different operations concurrently to expose interaction bugs:
374
+
375
+ ```ruby
376
+ threads = 60.times.map do |i|
377
+ Thread.new do
378
+ barrier.wait
379
+ case i % 3
380
+ when 0 # Operation type A
381
+ regular_operation
382
+ when 1 # Operation type B
383
+ pipeline_operation
384
+ when 2 # Operation type C
385
+ rapid_operations
386
+ end
387
+ end
388
+ end
389
+ ```
390
+
391
+ ### 4. Rapid Sequential Calls Pattern
392
+ Test that rapid-fire operations within threads don't corrupt state:
393
+
394
+ ```ruby
395
+ threads = 20.times.map do
396
+ Thread.new do
397
+ # Each thread hammers the system
398
+ 10.times { operation }
399
+ end
400
+ end
401
+
402
+ # Should have 200 results (20 × 10), all valid
403
+ [results.size, results.any?(nil)]
404
+ #=> [200, false]
405
+ ```
406
+
407
+ ### 5. Type and Method Validation Pattern
408
+ Ensure objects maintain their interface under concurrency:
409
+
410
+ ```ruby
411
+ # All results should respond to expected methods
412
+ results.all? { |r| r.respond_to?(:expected_method) }
413
+ #=> true
414
+
415
+ # All results should be correct type
416
+ results.all? { |r| r.is_a?(ExpectedClass) }
417
+ #=> true
418
+ ```
419
+
420
+ ## Architecture Insights
421
+
422
+ ### Why Familia Has These Issues
423
+
424
+ 1. **Module-level state**: Familia uses module instance variables for global config
425
+ 2. **Class-level state**: Each Horreum subclass has its own connection chain, field collections, etc.
426
+ 3. **Lazy initialization**: Performance optimization that trades safety for speed
427
+ 4. **No threading in original design**: Familia was designed for single-threaded use
428
+
429
+ ### The Fiber Safety Assumption
430
+
431
+ Familia uses fiber-local storage for transactions and pipelines:
432
+ ```ruby
433
+ Fiber[:familia_transaction] = conn
434
+ ```
435
+
436
+ This is safe **within a fiber** but the tests verify:
437
+ - No leakage between fibers
438
+ - Proper cleanup after exceptions
439
+ - Correct behavior with fiber switching
440
+
441
+ ## Reference Patterns
442
+
443
+ ### Safe Lazy Initialization
444
+ ```ruby
445
+ def connection_chain
446
+ @connection_chain_mutex ||= Mutex.new
447
+ @connection_chain_mutex.synchronize do
448
+ @connection_chain ||= build_connection_chain
449
+ end
450
+ end
451
+ ```
452
+
453
+ ### Safe Atomic Counter
454
+ ```ruby
455
+ @middleware_version = Concurrent::AtomicFixnum.new(0)
456
+
457
+ def increment_middleware_version!
458
+ @middleware_version.increment
459
+ end
460
+ ```
461
+
462
+ ### Safe Check-Then-Act
463
+ ```ruby
464
+ @registration_mutex = Mutex.new
465
+
466
+ def register_middleware
467
+ @registration_mutex.synchronize do
468
+ return if @middleware_registered
469
+ RedisClient.register(DatabaseLogger)
470
+ @middleware_registered = true
471
+ end
472
+ end
473
+ ```
474
+
475
+ ### Safe Caching
476
+ ```ruby
477
+ require 'concurrent-ruby'
478
+ @cache = ThreadSafe::Cache.new
479
+
480
+ def get_or_create(key)
481
+ @cache.fetch_or_store(key) { expensive_operation }
482
+ end
483
+ ```
484
+
485
+ ## Further Reading
486
+
487
+ - **Main Analysis**: `/THREAD_SAFETY_ANALYSIS.md` - Comprehensive findings and specific line numbers
488
+ - **Thread Safety Cheatsheet**: Project memory `cheatsheet_thread_safety_testing`
489
+ - **Concurrent Ruby**: https://github.com/ruby-concurrency/concurrent-ruby
490
+ - **Ruby Threads**: https://docs.ruby-lang.org/en/master/Thread.html
491
+
492
+ ## Philosophy
493
+
494
+ These tests exist because **threading bugs are hard to find and reproduce**. They might work 999 times and fail on the 1000th. These tests create the worst-case scenario on every run, making the invisible visible.
495
+
496
+ A test suite without thread safety tests is like a car without crash tests - it might work fine until someone gets hurt.