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
@@ -1,4 +1,6 @@
1
1
  # lib/familia/data_type/serialization.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  class DataType
@@ -6,46 +8,45 @@ module Familia
6
8
  # Serializes a value for storage in the database.
7
9
  #
8
10
  # @param val [Object] The value to be serialized.
9
- # @param strict_values [Boolean] Whether to enforce strict value
10
- # serialization (default: true).
11
- # @return [String, nil] The serialized representation of the value, or nil
12
- # if serialization fails.
11
+ # @return [String] The JSON-serialized representation of the value.
13
12
  #
14
- # @note When a class option is specified, it uses Familia.identifier_extractor
15
- # to extract the identifier from objects. Otherwise, it extracts identifiers
16
- # from Familia::Base instances or class names.
13
+ # Serialization priority:
14
+ # 1. Familia objects (Base instances or classes) extract identifier
15
+ # 2. All other values → JSON serialize for type preservation
17
16
  #
18
- # @example With a class option
19
- # serialize_value(User.new(name: "Cloe"), strict_values: false) #=> '{"name":"Cloe"}'
17
+ # This unifies behavior with Horreum fields (Issue #190), ensuring
18
+ # consistent type preservation across DataType and Horreum.
20
19
  #
21
- # @example Without a class option
22
- # serialize_value(123) #=> "123"
23
- # serialize_value("hello") #=> "hello"
20
+ # @example Familia object reference
21
+ # serialize_value(customer_obj) #=> "customer_123" (identifier)
24
22
  #
25
- # @raise [Familia::NotDistinguishableError] If serialization fails under strict
26
- # mode.
23
+ # @example Primitive values (JSON encoded)
24
+ # serialize_value(42) #=> "42"
25
+ # serialize_value("hello") #=> '"hello"'
26
+ # serialize_value(true) #=> "true"
27
+ # serialize_value(nil) #=> "null"
28
+ # serialize_value([1, 2, 3]) #=> "[1,2,3]"
27
29
  #
28
- def serialize_value(val, strict_values: true)
29
- prepared = nil
30
-
30
+ def serialize_value(val)
31
31
  Familia.trace :TOREDIS, nil, "#{val}<#{val.class}|#{opts[:class]}>" if Familia.debug?
32
32
 
33
- if opts[:class]
34
- prepared = Familia.identifier_extractor(opts[:class])
35
- Familia.ld " from opts[class] <#{opts[:class]}>: #{prepared || '<nil>'}"
33
+ # Priority 1: Handle Familia object references - extract identifier
34
+ # This preserves the existing behavior for storing object references
35
+ if val.is_a?(Familia::Base) || (val.is_a?(Class) && val.ancestors.include?(Familia::Base))
36
+ prepared = val.is_a?(Class) ? val.name : val.identifier
37
+ Familia.debug " Familia object: #{val.class} => #{prepared}"
38
+ return prepared
36
39
  end
37
40
 
38
- if prepared.nil?
39
- # Enforce strict values when no class option is specified
40
- prepared = Familia.identifier_extractor(val)
41
- Familia.ld " from <#{val.class}> => <#{prepared.class}>"
42
- end
41
+ # Priority 2: Everything else gets JSON serialized for type preservation
42
+ # This unifies behavior with Horreum fields (Issue #190)
43
+ prepared = Familia::JsonSerializer.dump(val)
44
+ Familia.debug " JSON serialized: #{val.class} => #{prepared}"
43
45
 
44
46
  if Familia.debug?
45
- Familia.trace :TOREDIS, nil, "#{val}<#{val.class}|#{opts[:class]}> => #{prepared}<#{prepared.class}>"
47
+ Familia.trace :TOREDIS, nil, "#{val}<#{val.class}> => #{prepared}<#{prepared.class}>"
46
48
  end
47
49
 
48
- Familia.warn "[#{self.class}#serialize_value] nil returned for #{opts[:class]}##{name}" if prepared.nil?
49
50
  prepared
50
51
  end
51
52
 
@@ -77,29 +78,43 @@ module Familia
77
78
  # replaced with nil.
78
79
  #
79
80
  def deserialize_values_with_nil(*values)
80
- Familia.ld "deserialize_values: (#{@opts}) #{values}"
81
+ Familia.debug "deserialize_values: (#{@opts}) #{values}"
81
82
  return [] if values.empty?
82
- return values.flatten unless @opts[:class]
83
83
 
84
- unless @opts[:class].respond_to?(load_method)
85
- raise Familia::Problem, "No such method: #{@opts[:class]}##{load_method}"
86
- end
84
+ # If a class option is specified, use class-based deserialization
85
+ if @opts[:class]
86
+ unless @opts[:class].respond_to?(load_method)
87
+ raise Familia::Problem, "No such method: #{@opts[:class]}##{load_method}"
88
+ end
87
89
 
88
- values.collect! do |obj|
89
- next if obj.nil?
90
+ values.collect! do |obj|
91
+ next if obj.nil?
90
92
 
91
- val = @opts[:class].send load_method, obj
92
- Familia.ld "[#{self.class}#deserialize_values] nil returned for #{@opts[:class]}##{name}" if val.nil?
93
+ val = @opts[:class].send load_method, obj
94
+ Familia.debug "[#{self.class}#deserialize_values] nil returned for #{@opts[:class]}##{name}" if val.nil?
93
95
 
94
- val
95
- rescue StandardError => e
96
- Familia.info val
97
- Familia.info "Parse error for #{dbkey} (#{load_method}): #{e.message}"
98
- Familia.info e.backtrace
99
- nil
96
+ val
97
+ rescue StandardError => e
98
+ Familia.info val
99
+ Familia.info "Parse error for #{dbkey} (#{load_method}): #{e.message}"
100
+ Familia.info e.backtrace
101
+ nil
102
+ end
103
+
104
+ return values
100
105
  end
101
106
 
102
- values
107
+ # No class option: JSON deserialize each value for type preservation (Issue #190)
108
+ values.flatten.collect do |obj|
109
+ next if obj.nil?
110
+
111
+ begin
112
+ Familia::JsonSerializer.parse(obj)
113
+ rescue Familia::SerializerError
114
+ # Fallback for legacy data stored without JSON encoding
115
+ obj
116
+ end
117
+ end
103
118
  end
104
119
 
105
120
  # Deserializes a single value from the database.
@@ -108,13 +123,15 @@ module Familia
108
123
  # @return [Object, nil] The deserialized object, the default value if
109
124
  # val is nil, or nil if deserialization fails.
110
125
  #
111
- # @note If no class option is specified, the original value is
112
- # returned unchanged.
126
+ # Deserialization priority:
127
+ # 1. Redis::Future objects → return as-is (transaction handling)
128
+ # 2. nil values → return default option value
129
+ # 3. Class option specified → use class-based deserialization
130
+ # 4. No class option → JSON parse for type preservation
113
131
  #
114
- # NOTE: Currently only the DataType class uses this method. Horreum
115
- # fields are a newer addition and don't support the full range of
116
- # deserialization options that DataType supports. It uses serialize_value
117
- # for serialization since everything becomes a string in Valkey.
132
+ # This unifies behavior with Horreum fields (Issue #190), ensuring
133
+ # consistent type preservation. Legacy data stored without JSON
134
+ # encoding is returned as-is.
118
135
  #
119
136
  def deserialize_value(val)
120
137
  # Handle Redis::Future objects during transactions first
@@ -122,10 +139,20 @@ module Familia
122
139
 
123
140
  return @opts[:default] if val.nil?
124
141
 
125
- return val unless @opts[:class]
142
+ # If a class option is specified, use the existing class-based deserialization
143
+ if @opts[:class]
144
+ ret = deserialize_values val
145
+ return ret&.first # return the object or nil
146
+ end
126
147
 
127
- ret = deserialize_values val
128
- ret&.first # return the object or nil
148
+ # No class option: JSON deserialize for type preservation (Issue #190)
149
+ # This unifies behavior with Horreum fields
150
+ begin
151
+ Familia::JsonSerializer.parse(val)
152
+ rescue Familia::SerializerError
153
+ # Fallback for legacy data stored without JSON encoding
154
+ val
155
+ end
129
156
  end
130
157
  end
131
158
  end
@@ -1,4 +1,6 @@
1
1
  # lib/familia/data_type/settings.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  class DataType
@@ -1,4 +1,6 @@
1
1
  # lib/familia/data_type/types/counter.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  class Counter < StringKey
@@ -1,4 +1,6 @@
1
1
  # lib/familia/data_type/types/hashkey.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  class HashKey < DataType
@@ -22,8 +24,8 @@ module Familia
22
24
  update_expiration
23
25
  ret
24
26
  rescue TypeError => e
25
- Familia.le "[hset]= #{e.message}"
26
- Familia.ld "[hset]= #{dbkey} #{field}=#{val}" if Familia.debug
27
+ Familia.error "[hset]= #{e.message}"
28
+ Familia.debug "[hset]= #{dbkey} #{field}=#{val}"
27
29
  echo :hset, Familia.pretty_stack(limit: 1) if Familia.debug # logs via echo to the db and back
28
30
  klass = val.class
29
31
  msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{dbkey}"
@@ -74,8 +76,8 @@ module Familia
74
76
  update_expiration if ret == 1
75
77
  ret
76
78
  rescue TypeError => e
77
- Familia.le "[hsetnx] #{e.message}"
78
- Familia.ld "[hsetnx] #{dbkey} #{field}=#{val}" if Familia.debug
79
+ Familia.error "[hsetnx] #{e.message}"
80
+ Familia.debug "[hsetnx] #{dbkey} #{field}=#{val}"
79
81
  echo :hsetnx, Familia.pretty_stack(limit: 1) if Familia.debug # logs via echo to the db and back
80
82
  klass = val.class
81
83
  msg = "Cannot store #{field} => #{val.inspect} (#{klass}) in #{dbkey}"
@@ -156,7 +158,7 @@ module Familia
156
158
  raise Familia::KeyNotFoundError, dbkey unless dbclient.exists(dbkey)
157
159
 
158
160
  fields = hgetall
159
- Familia.ld "[refresh!] #{self.class} #{dbkey} #{fields.keys}"
161
+ Familia.debug "[refresh!] #{self.class} #{dbkey} #{fields.keys}"
160
162
 
161
163
  # For HashKey, we update by merging the fresh data
162
164
  update(fields)
@@ -1,4 +1,6 @@
1
1
  # lib/familia/data_type/types/listkey.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  class ListKey < DataType
@@ -1,4 +1,6 @@
1
1
  # lib/familia/data_type/types/lock.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  class Lock < StringKey
@@ -1,4 +1,6 @@
1
1
  # lib/familia/data_type/types/sorted_set.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  class SortedSet < DataType
@@ -127,7 +129,7 @@ module Familia
127
129
  alias add_element add
128
130
 
129
131
  def score(val)
130
- ret = dbclient.zscore dbkey, serialize_value(val, strict_values: false)
132
+ ret = dbclient.zscore dbkey, serialize_value(val)
131
133
  ret&.to_f
132
134
  end
133
135
  alias [] score
@@ -140,13 +142,13 @@ module Familia
140
142
 
141
143
  # rank of member +v+ when ordered lowest to highest (starts at 0)
142
144
  def rank(v)
143
- ret = dbclient.zrank dbkey, serialize_value(v, strict_values: false)
145
+ ret = dbclient.zrank dbkey, serialize_value(v)
144
146
  ret&.to_i
145
147
  end
146
148
 
147
149
  # rank of member +v+ when ordered highest to lowest (starts at 0)
148
150
  def revrank(v)
149
- ret = dbclient.zrevrank dbkey, serialize_value(v, strict_values: false)
151
+ ret = dbclient.zrevrank dbkey, serialize_value(v)
150
152
  ret&.to_i
151
153
  end
152
154
 
@@ -267,7 +269,7 @@ module Familia
267
269
  end
268
270
 
269
271
  def increment(val, by = 1)
270
- dbclient.zincrby(dbkey, by, val).to_i
272
+ dbclient.zincrby(dbkey, by, serialize_value(val)).to_i
271
273
  end
272
274
  alias incr increment
273
275
  alias incrby increment
@@ -283,12 +285,7 @@ module Familia
283
285
  # @return [Integer] The number of members that were removed (0 or 1)
284
286
  def remove_element(value)
285
287
  Familia.trace :REMOVE_ELEMENT, nil, "#{value}<#{value.class}>" if Familia.debug?
286
- # We use `strict_values: false` here to allow for the deletion of values
287
- # that are in the sorted set. If it's a horreum object, the value is
288
- # the identifier and not a serialized version of the object. So either
289
- # the value exists in the sorted set or it doesn't -- we don't need to
290
- # raise an error if it's not found.
291
- dbclient.zrem dbkey, serialize_value(value, strict_values: false)
288
+ dbclient.zrem dbkey, serialize_value(value)
292
289
  end
293
290
  alias remove remove_element # deprecated
294
291
 
@@ -1,9 +1,33 @@
1
1
  # lib/familia/data_type/types/stringkey.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  class StringKey < DataType
5
7
  def init; end
6
8
 
9
+ # StringKey uses raw string serialization (not JSON) because Redis string
10
+ # operations like INCR, DECR, APPEND operate on raw values.
11
+ # This overrides the base JSON serialization from DataType.
12
+ def serialize_value(val)
13
+ Familia.trace :TOREDIS, nil, "#{val}<#{val.class}>" if Familia.debug?
14
+
15
+ # Handle Familia object references - extract identifier
16
+ if val.is_a?(Familia::Base) || (val.is_a?(Class) && val.ancestors.include?(Familia::Base))
17
+ return val.is_a?(Class) ? val.name : val.identifier
18
+ end
19
+
20
+ # StringKey uses raw string conversion for Redis compatibility
21
+ val.to_s
22
+ end
23
+
24
+ # StringKey returns raw values (not JSON parsed)
25
+ def deserialize_value(val)
26
+ return val if val.is_a?(Redis::Future)
27
+ return @opts[:default] if val.nil?
28
+ val
29
+ end
30
+
7
31
  # Returns the number of elements in the list
8
32
  # @return [Integer] number of elements
9
33
  def char_count
@@ -1,4 +1,6 @@
1
1
  # lib/familia/data_type/types/unsorted_set.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  # Familia::UnsortedSet
@@ -1,4 +1,6 @@
1
1
  # lib/familia/data_type.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require_relative 'data_type/class_methods'
4
6
  require_relative 'data_type/settings'
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/encrypted_data.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Encryption
@@ -15,10 +17,10 @@ module Familia
15
17
  # Check for required fields
16
18
  required_fields = %i[algorithm nonce ciphertext auth_tag key_version]
17
19
  result = required_fields.all? { |field| parsed.key?(field) }
18
- Familia.ld "[valid?] result: #{result}, parsed: #{parsed}, required: #{required_fields}"
20
+ Familia.debug "[valid?] result: #{result}, parsed: #{parsed}, required: #{required_fields}"
19
21
  result
20
22
  rescue Familia::SerializerError => e
21
- Familia.ld "[valid?] JSON error: #{e.message}"
23
+ Familia.debug "[valid?] JSON error: #{e.message}"
22
24
  false
23
25
  end
24
26
  end
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/manager.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Encryption
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/provider.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Encryption
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/providers/aes_gcm_provider.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # ⚠️ RUBY MEMORY SAFETY WARNING ⚠️
4
6
  #
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # ⚠️ PROTOTYPE IMPLEMENTATION - NOT FOR PRODUCTION USE ⚠️
4
6
  #
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/providers/xchacha20_poly1305_provider.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # ⚠️ RUBY MEMORY SAFETY WARNING ⚠️
4
6
  #
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption/registry.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  module Encryption
@@ -1,5 +1,7 @@
1
1
  # lib/familia/encryption/request_cache.rb
2
2
  #
3
+ # frozen_string_literal: true
4
+
3
5
  # Request-scoped caching for encryption keys (if needed for performance)
4
6
  # This should ONLY be enabled if performance testing shows it's necessary
5
7
  #
@@ -1,4 +1,6 @@
1
1
  # lib/familia/encryption.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require 'base64'
4
6
  require 'oj'
@@ -60,9 +62,14 @@ module Familia
60
62
  # end
61
63
  class << self
62
64
  # Get or create a manager with specific algorithm
65
+ #
66
+ # Thread-safe lazy initialization using Concurrent::Map to ensure
67
+ # only a single Manager instance is created per algorithm even under
68
+ # concurrent encryption/decryption requests.
69
+ #
63
70
  def manager(algorithm: nil)
64
- @managers ||= {}
65
- @managers[algorithm] ||= Manager.new(algorithm: algorithm)
71
+ @managers ||= Concurrent::Map.new
72
+ @managers.fetch_or_store(algorithm) { Manager.new(algorithm: algorithm) }
66
73
  end
67
74
 
68
75
  # Quick encryption with auto-selected best provider
@@ -1,5 +1,7 @@
1
1
  # lib/familia/errors.rb
2
2
  #
3
+ # frozen_string_literal: true
4
+
3
5
  module Familia
4
6
  # Base exception class for all Familia errors
5
7
  class Problem < RuntimeError; end
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/autoloader.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # rubocop:disable Style/ClassAndModuleChildren
4
6
  module Familia::Features
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/encrypted_fields/concealed_string.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  # ConcealedString
4
6
  #
@@ -1,3 +1,7 @@
1
+ # lib/familia/features/encrypted_fields/encrypted_field_type.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # lib/familia/field_types/encrypted_field_type.rb
2
6
 
3
7
  require_relative '../../field_type'
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/encrypted_fields.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require_relative 'encrypted_fields/encrypted_field_type'
4
6
 
@@ -341,8 +343,6 @@ module Familia
341
343
  # Check if this instance has any encrypted fields with values
342
344
  #
343
345
  # @return [Boolean] true if any encrypted fields have values
344
- #
345
- # TODO: Missing test coverage
346
346
  def encrypted_data?
347
347
  self.class.encrypted_fields.any? do |field_name|
348
348
  field_value = instance_variable_get("@#{field_name}")
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/expiration/extensions.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  module Familia
4
6
  # Add a default update_expiration method for all classes that include
@@ -24,7 +26,7 @@ module Familia
24
26
  # @example MyModel.new.update_expiration(expiration: 3600) # => nothing happens
25
27
  #
26
28
  def update_expiration(expiration: nil)
27
- Familia.ld <<~LOG
29
+ Familia.debug <<~LOG
28
30
  [update_expiration] Expiration feature not enabled for #{self.class}.
29
31
  Key: #{dbkey} Arg: #{expiration} (caller: #{caller(1..1)})
30
32
  LOG
@@ -1,4 +1,6 @@
1
1
  # lib/familia/features/expiration.rb
2
+ #
3
+ # frozen_string_literal: true
2
4
 
3
5
  require_relative 'expiration/extensions'
4
6
 
@@ -252,13 +254,13 @@ module Familia
252
254
 
253
255
  # Handle cascading expiration to related data structures
254
256
  if self.class.relations?
255
- Familia.ld "[update_expiration] #{self.class} has relations: #{self.class.related_fields.keys}"
257
+ Familia.debug "[update_expiration] #{self.class} has relations: #{self.class.related_fields.keys}"
256
258
  self.class.related_fields.each do |name, definition|
257
259
  # Skip relations that don't have their own expiration settings
258
260
  next if definition.opts[:default_expiration].nil?
259
261
 
260
262
  obj = send(name)
261
- Familia.ld "[update_expiration] Updating expiration for #{name} (#{obj.dbkey}) to #{expiration}"
263
+ Familia.debug "[update_expiration] Updating expiration for #{name} (#{obj.dbkey}) to #{expiration}"
262
264
  obj.update_expiration(expiration: expiration)
263
265
  end
264
266
  end
@@ -280,11 +282,17 @@ module Familia
280
282
  # If zero, simply skip setting an expiry for this key. If we were to set
281
283
  # 0, Valkey/Redis would drop the key immediately.
282
284
  if expiration.zero?
283
- Familia.ld "[update_expiration] No expiration for #{self.class} (#{dbkey})"
285
+ Familia.debug "[update_expiration] No expiration for #{self.class} (#{dbkey})"
284
286
  return true
285
287
  end
286
288
 
287
- Familia.ld "[update_expiration] Expires #{dbkey} in #{expiration} seconds"
289
+ # Structured TTL operation logging
290
+ Familia.debug "TTL updated",
291
+ operation: :expire,
292
+ key: dbkey,
293
+ ttl_seconds: expiration,
294
+ class: self.class.name,
295
+ identifier: (identifier rescue nil)
288
296
 
289
297
  # The Valkey/Redis' EXPIRE command returns 1 if the timeout was set, 0
290
298
  # if key does not exist or the timeout could not be set. Via redis-rb,