familia 2.0.0.pre19 → 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 (372) 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 +177 -112
  8. data/CLAUDE.md +28 -1
  9. data/Gemfile +1 -1
  10. data/Gemfile.lock +20 -17
  11. data/bin/try +16 -0
  12. data/bin/tryouts +16 -0
  13. data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
  14. data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
  15. data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
  16. data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
  17. data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
  18. data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -0
  19. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  20. data/docs/guides/encryption.md +486 -0
  21. data/docs/guides/feature-encrypted-fields.md +123 -7
  22. data/docs/guides/feature-expiration.md +161 -117
  23. data/docs/guides/feature-external-identifiers.md +415 -443
  24. data/docs/guides/feature-object-identifiers.md +400 -269
  25. data/docs/guides/feature-quantization.md +120 -6
  26. data/docs/guides/feature-relationships-indexing.md +318 -0
  27. data/docs/guides/feature-relationships-methods.md +146 -604
  28. data/docs/guides/feature-relationships-participation.md +263 -0
  29. data/docs/guides/feature-relationships.md +118 -136
  30. data/docs/guides/feature-system-devs.md +176 -693
  31. data/docs/guides/feature-system.md +119 -6
  32. data/docs/guides/feature-transient-fields.md +81 -0
  33. data/docs/guides/field-system.md +778 -0
  34. data/docs/guides/index.md +32 -15
  35. data/docs/guides/logging.md +187 -0
  36. data/docs/guides/optimized-loading.md +674 -0
  37. data/docs/guides/thread-safety-monitoring.md +61 -0
  38. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  39. data/docs/migrating/v2.0.0-pre22.md +241 -0
  40. data/docs/overview.md +7 -9
  41. data/docs/reference/api-technical.md +267 -320
  42. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  43. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  44. data/examples/autoloader/mega_customer.rb +2 -0
  45. data/examples/datatype_standalone.rb +4 -3
  46. data/examples/encrypted_fields.rb +2 -1
  47. data/examples/json_usage_patterns.rb +2 -0
  48. data/examples/relationships.rb +3 -0
  49. data/examples/safe_dump.rb +2 -1
  50. data/examples/sampling_demo.rb +53 -0
  51. data/examples/single_connection_transaction_confusions.rb +2 -1
  52. data/familia.gemspec +2 -1
  53. data/lib/familia/base.rb +2 -0
  54. data/lib/familia/connection/behavior.rb +2 -0
  55. data/lib/familia/connection/handlers.rb +2 -0
  56. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  57. data/lib/familia/connection/middleware.rb +34 -24
  58. data/lib/familia/connection/operation_core.rb +2 -0
  59. data/lib/familia/connection/operations.rb +2 -0
  60. data/lib/familia/connection/pipelined_core.rb +2 -0
  61. data/lib/familia/connection/transaction_core.rb +68 -0
  62. data/lib/familia/connection.rb +18 -3
  63. data/lib/familia/data_type/class_methods.rb +3 -1
  64. data/lib/familia/data_type/connection.rb +2 -0
  65. data/lib/familia/data_type/database_commands.rb +2 -0
  66. data/lib/familia/data_type/serialization.rb +6 -4
  67. data/lib/familia/data_type/settings.rb +2 -0
  68. data/lib/familia/data_type/types/counter.rb +2 -0
  69. data/lib/familia/data_type/types/hashkey.rb +7 -5
  70. data/lib/familia/data_type/types/listkey.rb +2 -0
  71. data/lib/familia/data_type/types/lock.rb +2 -0
  72. data/lib/familia/data_type/types/sorted_set.rb +2 -0
  73. data/lib/familia/data_type/types/stringkey.rb +2 -0
  74. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  75. data/lib/familia/data_type.rb +2 -0
  76. data/lib/familia/encryption/encrypted_data.rb +4 -2
  77. data/lib/familia/encryption/manager.rb +2 -0
  78. data/lib/familia/encryption/provider.rb +2 -0
  79. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  80. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  81. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  82. data/lib/familia/encryption/registry.rb +2 -0
  83. data/lib/familia/encryption/request_cache.rb +2 -0
  84. data/lib/familia/encryption.rb +9 -2
  85. data/lib/familia/errors.rb +2 -0
  86. data/lib/familia/features/autoloader.rb +2 -0
  87. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  88. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  89. data/lib/familia/features/encrypted_fields.rb +2 -2
  90. data/lib/familia/features/expiration/extensions.rb +3 -1
  91. data/lib/familia/features/expiration.rb +12 -4
  92. data/lib/familia/features/external_identifier.rb +33 -7
  93. data/lib/familia/features/object_identifier.rb +2 -0
  94. data/lib/familia/features/quantization.rb +3 -1
  95. data/lib/familia/features/relationships/README.md +3 -1
  96. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  97. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
  98. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  99. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +89 -21
  100. data/lib/familia/features/relationships/indexing.rb +3 -0
  101. data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
  102. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  103. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  104. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  105. data/lib/familia/features/relationships/participation.rb +155 -69
  106. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  107. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  108. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  109. data/lib/familia/features/relationships.rb +5 -3
  110. data/lib/familia/features/safe_dump.rb +2 -0
  111. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  112. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  113. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  114. data/lib/familia/features/transient_fields.rb +2 -0
  115. data/lib/familia/features.rb +2 -0
  116. data/lib/familia/field_type.rb +3 -1
  117. data/lib/familia/horreum/connection.rb +17 -1
  118. data/lib/familia/horreum/database_commands.rb +2 -0
  119. data/lib/familia/horreum/definition.rb +16 -6
  120. data/lib/familia/horreum/management.rb +212 -42
  121. data/lib/familia/horreum/persistence.rb +176 -108
  122. data/lib/familia/horreum/related_fields.rb +2 -0
  123. data/lib/familia/horreum/serialization.rb +23 -4
  124. data/lib/familia/horreum/settings.rb +2 -0
  125. data/lib/familia/horreum/utils.rb +2 -0
  126. data/lib/familia/horreum.rb +15 -1
  127. data/lib/familia/identifier_extractor.rb +2 -0
  128. data/lib/familia/instrumentation.rb +156 -0
  129. data/lib/familia/json_serializer.rb +2 -0
  130. data/lib/familia/logging.rb +92 -32
  131. data/lib/familia/refinements/dear_json.rb +2 -0
  132. data/lib/familia/refinements/stylize_words.rb +2 -14
  133. data/lib/familia/refinements/time_literals.rb +2 -0
  134. data/lib/familia/refinements.rb +2 -0
  135. data/lib/familia/secure_identifier.rb +10 -2
  136. data/lib/familia/settings.rb +2 -0
  137. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  138. data/lib/familia/thread_safety/monitor.rb +328 -0
  139. data/lib/familia/utils.rb +13 -0
  140. data/lib/familia/verifiable_identifier.rb +3 -1
  141. data/lib/familia/version.rb +3 -1
  142. data/lib/familia.rb +31 -4
  143. data/lib/middleware/database_command_counter.rb +152 -0
  144. data/lib/middleware/database_logger.rb +295 -170
  145. data/lib/multi_result.rb +2 -0
  146. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  147. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  148. data/try/edge_cases/json_serialization_try.rb +2 -0
  149. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  150. data/try/edge_cases/race_conditions_try.rb +4 -0
  151. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  152. data/try/edge_cases/string_coercion_try.rb +2 -0
  153. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  154. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  155. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  156. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  157. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  158. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  159. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  160. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  161. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  162. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  163. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  164. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  165. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  166. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  167. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  168. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  169. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  170. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  171. data/try/features/encryption/config_persistence_try.rb +4 -0
  172. data/try/features/encryption/core_try.rb +4 -0
  173. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  174. data/try/features/encryption/module_loading_try.rb +4 -0
  175. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  176. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  177. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  178. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  179. data/try/features/expiration/expiration_try.rb +4 -0
  180. data/try/features/external_identifier/external_identifier_try.rb +171 -8
  181. data/try/features/feature_dependencies_try.rb +2 -0
  182. data/try/features/feature_improvements_try.rb +2 -0
  183. data/try/features/field_groups_try.rb +2 -0
  184. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  185. data/try/features/object_identifier/object_identifier_try.rb +2 -0
  186. data/try/features/quantization/quantization_try.rb +4 -0
  187. data/try/features/real_feature_integration_try.rb +2 -0
  188. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  189. data/try/features/relationships/indexing_rebuild_try.rb +600 -0
  190. data/try/features/relationships/indexing_try.rb +2 -0
  191. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  192. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  193. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  194. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  195. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  196. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  197. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  198. data/try/features/relationships/relationships_api_changes_try.rb +2 -0
  199. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  200. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  201. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  202. data/try/features/relationships/relationships_performance_try.rb +4 -0
  203. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  204. data/try/features/relationships/relationships_try.rb +6 -4
  205. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  206. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  207. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  208. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  209. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  210. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  211. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  212. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  213. data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
  214. data/try/integration/connection/handler_constraints_try.rb +4 -0
  215. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  216. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  217. data/try/integration/connection/operation_mode_guards_try.rb +4 -0
  218. data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
  219. data/try/integration/connection/pools_try.rb +4 -0
  220. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  221. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  222. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  223. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  224. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  225. data/try/integration/connection/transaction_modes_try.rb +4 -0
  226. data/try/integration/conventional_inheritance_try.rb +4 -0
  227. data/try/integration/create_method_try.rb +4 -0
  228. data/try/integration/cross_component_try.rb +4 -0
  229. data/try/integration/data_types/datatype_pipelines_try.rb +4 -0
  230. data/try/integration/data_types/datatype_transactions_try.rb +4 -0
  231. data/try/integration/database_consistency_try.rb +4 -0
  232. data/try/integration/familia_extended_try.rb +4 -0
  233. data/try/integration/familia_members_methods_try.rb +4 -0
  234. data/try/integration/models/customer_safe_dump_try.rb +4 -0
  235. data/try/integration/models/customer_try.rb +4 -0
  236. data/try/integration/models/datatype_base_try.rb +4 -0
  237. data/try/integration/models/familia_object_try.rb +4 -0
  238. data/try/integration/persistence_operations_try.rb +4 -0
  239. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  240. data/try/integration/save_methods_consistency_try.rb +241 -0
  241. data/try/integration/scenarios_try.rb +4 -0
  242. data/try/integration/secure_identifier_try.rb +4 -0
  243. data/try/integration/transaction_safety_core_try.rb +176 -0
  244. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  245. data/try/integration/verifiable_identifier_try.rb +4 -0
  246. data/try/investigation/pipeline_routing/README.md +228 -0
  247. data/try/performance/benchmarks_try.rb +4 -0
  248. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  249. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  250. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  251. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  252. data/try/support/debugging/debug_aad_process.rb +3 -0
  253. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  254. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  255. data/try/support/debugging/debug_context_aad.rb +3 -0
  256. data/try/support/debugging/debug_context_simple.rb +3 -0
  257. data/try/support/debugging/debug_cross_context.rb +3 -0
  258. data/try/support/debugging/debug_database_load.rb +3 -0
  259. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  260. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  261. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  262. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  263. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  264. data/try/support/debugging/debug_load_path.rb +3 -0
  265. data/try/support/debugging/debug_method_definition.rb +3 -0
  266. data/try/support/debugging/debug_method_resolution.rb +3 -0
  267. data/try/support/debugging/debug_minimal.rb +3 -0
  268. data/try/support/debugging/debug_provider.rb +3 -0
  269. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  270. data/try/support/debugging/debug_string_class.rb +3 -0
  271. data/try/support/debugging/debug_test.rb +3 -0
  272. data/try/support/debugging/debug_test_design.rb +3 -0
  273. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  274. data/try/support/debugging/provider_diagnostics.rb +4 -0
  275. data/try/support/helpers/test_cleanup.rb +4 -0
  276. data/try/support/helpers/test_helpers.rb +5 -0
  277. data/try/support/memory/memory_basic_test.rb +4 -0
  278. data/try/support/memory/memory_detailed_test.rb +4 -0
  279. data/try/support/memory/memory_search_for_string.rb +4 -0
  280. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  281. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  282. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  283. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  284. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  285. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  286. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  287. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  288. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  289. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  290. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  291. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  292. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  293. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  294. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  295. data/try/thread_safety/README.md +496 -0
  296. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  297. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  298. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  299. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  300. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  301. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  302. data/try/thread_safety/field_registration_race_try.rb +222 -0
  303. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  304. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  305. data/try/thread_safety/module_config_race_try.rb +175 -0
  306. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  307. data/try/unit/core/autoloader_try.rb +4 -0
  308. data/try/unit/core/base_enhancements_try.rb +4 -0
  309. data/try/unit/core/connection_try.rb +4 -0
  310. data/try/unit/core/errors_try.rb +4 -0
  311. data/try/unit/core/extensions_try.rb +4 -0
  312. data/try/unit/core/familia_logger_try.rb +2 -0
  313. data/try/unit/core/familia_try.rb +4 -0
  314. data/try/unit/core/middleware_sampling_try.rb +335 -0
  315. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  316. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  317. data/try/unit/core/middleware_try.rb +4 -0
  318. data/try/unit/core/settings_try.rb +4 -0
  319. data/try/unit/core/time_utils_try.rb +4 -0
  320. data/try/unit/core/tools_try.rb +4 -0
  321. data/try/unit/core/utils_try.rb +37 -0
  322. data/try/unit/data_types/boolean_try.rb +4 -0
  323. data/try/unit/data_types/counter_try.rb +4 -0
  324. data/try/unit/data_types/datatype_base_try.rb +4 -0
  325. data/try/unit/data_types/hash_try.rb +4 -0
  326. data/try/unit/data_types/list_try.rb +4 -0
  327. data/try/unit/data_types/lock_try.rb +4 -0
  328. data/try/unit/data_types/sorted_set_try.rb +4 -0
  329. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  330. data/try/unit/data_types/string_try.rb +4 -0
  331. data/try/unit/data_types/unsortedset_try.rb +4 -0
  332. data/try/unit/familia_resolve_class_try.rb +116 -0
  333. data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
  334. data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
  335. data/try/unit/horreum/base_try.rb +4 -0
  336. data/try/unit/horreum/class_methods_try.rb +4 -0
  337. data/try/unit/horreum/commands_try.rb +4 -0
  338. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  339. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +4 -0
  340. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  341. data/try/unit/horreum/field_categories_try.rb +4 -0
  342. data/try/unit/horreum/field_definition_try.rb +4 -0
  343. data/try/unit/horreum/initialization_try.rb +4 -0
  344. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  345. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  346. data/try/unit/horreum/relations_try.rb +4 -0
  347. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  348. data/try/unit/horreum/serialization_try.rb +4 -0
  349. data/try/unit/horreum/settings_try.rb +4 -0
  350. data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
  351. data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
  352. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  353. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  354. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  355. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  356. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  357. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  358. data/try/unit/thread_safety_monitor_try.rb +149 -0
  359. metadata +72 -17
  360. data/.github/workflows/code-quality.yml +0 -138
  361. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
  362. data/changelog.d/20251011_203905_delano_next.rst +0 -30
  363. data/changelog.d/20251011_212633_delano_next.rst +0 -13
  364. data/changelog.d/20251011_221253_delano_next.rst +0 -26
  365. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  366. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  367. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  368. data/docs/archive/README.md +0 -64
  369. data/docs/archive/api-reference.md +0 -333
  370. data/docs/guides/core-field-system.md +0 -806
  371. data/docs/guides/implementation.md +0 -276
  372. data/docs/guides/security-model.md +0 -183
@@ -296,8 +296,11 @@ logger.info("User key: #{user.api_key}") # => "User key: [CONCEALED]"
296
296
  user_json = user.to_json
297
297
  # All encrypted fields appear as "[CONCEALED]" in JSON
298
298
 
299
- # Explicit access when needed
300
- actual_key = user.api_key.reveal # => "sk-1234567890abcdef"
299
+ # Explicit access when needed (requires block)
300
+ user.api_key.reveal do |actual_key|
301
+ # Use actual_key here: "sk-1234567890abcdef"
302
+ process_key(actual_key)
303
+ end
301
304
  ```
302
305
 
303
306
  ### String Operations
@@ -313,15 +316,16 @@ api_key.size # => 11
313
316
  api_key == "[CONCEALED]" # => true
314
317
  api_key.start_with?("[CONCEALED]") # => true
315
318
 
316
- # Reveal for actual operations
317
- actual_key = api_key.reveal
318
- actual_key.length # => 17 (actual key length)
319
- actual_key.start_with?("sk-") # => true
319
+ # Reveal for actual operations (requires block)
320
+ api_key.reveal do |actual_key|
321
+ actual_key.length # => 17 (actual key length)
322
+ actual_key.start_with?("sk-") # => true
323
+ end
320
324
  ```
321
325
 
322
326
  > **⚠️ Important**
323
327
  >
324
- > Always use `.reveal` explicitly when you need the actual value. This makes it obvious in code reviews where sensitive data is being accessed.
328
+ > Always use `.reveal { |value| ... }` with a block when you need the actual value. This makes it obvious in code reviews where sensitive data is being accessed and prevents accidental copies.
325
329
 
326
330
  ## Performance Optimization
327
331
 
@@ -683,6 +687,97 @@ class FastEncryptedModelTest < Minitest::Test
683
687
  end
684
688
  ```
685
689
 
690
+ ## Instance Methods
691
+
692
+ ### Core Encrypted Field Methods
693
+
694
+ #### `encrypted_data?`
695
+ Check if instance has any encrypted fields with values.
696
+
697
+ ```ruby
698
+ vault = Vault.new(secret_key: "value")
699
+ vault.encrypted_data? # => true
700
+
701
+ empty_vault = Vault.new
702
+ empty_vault.encrypted_data? # => false
703
+ ```
704
+
705
+ #### `clear_encrypted_fields!`
706
+ Clear all encrypted field values from memory.
707
+
708
+ ```ruby
709
+ vault.clear_encrypted_fields!
710
+ vault.encrypted_fields_cleared? # => true
711
+ ```
712
+
713
+ #### `re_encrypt_fields!`
714
+ Re-encrypt all encrypted fields with current encryption settings (useful for key rotation).
715
+
716
+ ```ruby
717
+ vault.re_encrypt_fields!
718
+ vault.save # Persists re-encrypted data
719
+ ```
720
+
721
+ #### `encrypted_fields_status`
722
+ Get encryption status for all encrypted fields.
723
+
724
+ ```ruby
725
+ vault.encrypted_fields_status
726
+ # => {
727
+ # secret_key: { encrypted: true, algorithm: "xchacha20poly1305", cleared: false },
728
+ # api_token: { encrypted: true, cleared: true }
729
+ # }
730
+ ```
731
+
732
+ ### Class Methods
733
+
734
+ #### `encrypted_field?(field_name)`
735
+ Check if a field is encrypted.
736
+
737
+ ```ruby
738
+ Vault.encrypted_field?(:secret_key) # => true
739
+ Vault.encrypted_field?(:name) # => false
740
+ ```
741
+
742
+ #### `encryption_info`
743
+ Get encryption algorithm information.
744
+
745
+ ```ruby
746
+ Vault.encryption_info
747
+ # => {
748
+ # algorithm: "xchacha20poly1305",
749
+ # key_size: 32,
750
+ # nonce_size: 24,
751
+ # tag_size: 16
752
+ # }
753
+ ```
754
+
755
+ ### ConcealedString Methods
756
+
757
+ #### `reveal { |plaintext| ... }`
758
+ Primary API for accessing decrypted values (requires block).
759
+
760
+ ```ruby
761
+ user.api_token.reveal do |token|
762
+ HTTP.post('/api', headers: { 'X-Token' => token })
763
+ end
764
+ ```
765
+
766
+ #### `belongs_to_context?(record, field_name)`
767
+ Validate that ConcealedString belongs to the given record context.
768
+
769
+ ```ruby
770
+ concealed.belongs_to_context?(user, :api_token) # => true/false
771
+ ```
772
+
773
+ #### `cleared?` and `clear!`
774
+ Memory management for encrypted data.
775
+
776
+ ```ruby
777
+ concealed.clear!
778
+ concealed.cleared? # => true
779
+ ```
780
+
686
781
  ## Production Considerations
687
782
 
688
783
  ### Monitoring and Alerting
@@ -776,6 +871,27 @@ end
776
871
 
777
872
  ---
778
873
 
874
+ ## Configuration Reference
875
+
876
+ ### Additional Configuration Options
877
+
878
+ ```ruby
879
+ Familia.configure do |config|
880
+ config.encryption_keys = { v1: key, v2: new_key }
881
+ config.current_key_version = :v2
882
+ config.encryption_personalization = 'MyApp-2024' # XChaCha20 only
883
+ end
884
+
885
+ # Validate configuration
886
+ Familia::Encryption.validate_configuration!
887
+ ```
888
+
889
+ ### Error Types
890
+
891
+ - `Familia::EncryptionError` - General encryption/decryption failures
892
+ - `Familia::SerializerError` - Serialization safety violations
893
+ - `SecurityError` - Context validation or cleared data access
894
+
779
895
  ## See Also
780
896
 
781
897
  - **[Overview](../overview.md#encrypted-fields)** - Conceptual introduction to encrypted fields
@@ -16,7 +16,7 @@ Familia uses a three-tier expiration system:
16
16
 
17
17
  ### Cascading Expiration
18
18
 
19
- When a Horreum object has related fields (DataTypes), the expiration feature automatically cascades TTL updates to all related objects, ensuring consistent data lifecycle management.
19
+ When a Horreum object has related fields (DataTypes), the expiration feature automatically cascades TTL updates to related objects that have their own `default_expiration` settings defined. This ensures consistent data lifecycle management while respecting per-field expiration policies.
20
20
 
21
21
  ## Basic Usage
22
22
 
@@ -103,21 +103,60 @@ class Customer < Familia::Horreum
103
103
 
104
104
  identifier_field :customer_id
105
105
  field :customer_id, :name, :email
106
- list :recent_orders # Will get same TTL
107
- set :favorite_categories # Will get same TTL
108
- hashkey :preferences # Will get same TTL
106
+ list :recent_orders, default_expiration: 12.hours # Will cascade
107
+ set :favorite_categories # Will NOT cascade (no default_expiration)
108
+ hashkey :preferences, default_expiration: 6.hours # Will cascade
109
109
  end
110
110
 
111
111
  customer = Customer.new(customer_id: 'cust_123')
112
112
  customer.save
113
113
 
114
- # This will set TTL on the main object AND all related fields
114
+ # This will set TTL on the main object AND related fields with default_expiration
115
115
  customer.update_expiration(expiration: 12.hours)
116
116
  # Sets expiration on:
117
- # - customer:cust_123 (main hash)
118
- # - customer:cust_123:recent_orders (list)
119
- # - customer:cust_123:favorite_categories (set)
120
- # - customer:cust_123:preferences (hashkey)
117
+ # - customer:cust_123 (main hash) - 12 hours
118
+ # - customer:cust_123:recent_orders (list) - 12 hours
119
+ # - customer:cust_123:preferences (hashkey) - 12 hours
120
+ # - customer:cust_123:favorite_categories (set) - NO expiration (no default_expiration)
121
+ ```
122
+
123
+ ### TTL Status Checking
124
+
125
+ ```ruby
126
+ session = UserSession.find('session_123')
127
+
128
+ # Check remaining TTL (returns seconds or special values)
129
+ ttl_seconds = session.ttl
130
+ case ttl_seconds
131
+ when -1
132
+ puts "Session never expires (no TTL set)"
133
+ when -2
134
+ puts "Session key doesn't exist"
135
+ when 0
136
+ puts "Session has expired"
137
+ else
138
+ puts "Session expires in #{ttl_seconds} seconds"
139
+ end
140
+
141
+ # Convenience methods for TTL status
142
+ session.expires? # => true if TTL is set
143
+ session.expired? # => true if TTL <= 0
144
+ session.expired?(5.minutes) # => true if TTL <= 300 seconds
145
+ ```
146
+
147
+ ### Expiration Extension and Removal
148
+
149
+ ```ruby
150
+ session = UserSession.find('active_session')
151
+
152
+ # Extend current expiration by additional time
153
+ session.extend_expiration(15.minutes) # Adds 15 minutes to current TTL
154
+
155
+ # Remove expiration entirely (persist indefinitely)
156
+ session.persist! # Removes TTL, data won't expire
157
+
158
+ # Zero expiration also persists data (equivalent to persist!)
159
+ session.update_expiration(expiration: 0) # No expiration set
121
160
  ```
122
161
 
123
162
  ### Conditional Expiration
@@ -145,21 +184,6 @@ class AnalyticsEvent < Familia::Horreum
145
184
  end
146
185
  ```
147
186
 
148
- ### Zero Expiration (Persistent Data)
149
-
150
- ```ruby
151
- class PermanentRecord < Familia::Horreum
152
- feature :expiration
153
- default_expiration 0 # Never expires
154
-
155
- field :permanent_data
156
- end
157
-
158
- # Zero expiration means data persists indefinitely
159
- record = PermanentRecord.new
160
- record.update_expiration # No-op, data won't expire
161
- ```
162
-
163
187
  ## Integration Patterns
164
188
 
165
189
  ### Rails Integration
@@ -255,6 +279,8 @@ when -1
255
279
  puts "Session never expires"
256
280
  when -2
257
281
  puts "Session key doesn't exist"
282
+ when 0
283
+ puts "Session has expired"
258
284
  when 0..3600
259
285
  puts "Session expires in #{ttl_seconds / 60} minutes"
260
286
  else
@@ -282,7 +308,7 @@ class SessionManager
282
308
  def self.make_sessions_permanent
283
309
  # Remove expiration from all sessions
284
310
  UserSession.all.each do |session|
285
- session.persist # Remove TTL entirely
311
+ session.persist! # Remove TTL entirely
286
312
  end
287
313
  end
288
314
  end
@@ -333,7 +359,9 @@ pipeline = redis.pipelined do |pipe|
333
359
  pipe.expire(session.dbkey, 3600)
334
360
 
335
361
  # Also expire related fields if needed
336
- session.class.related_fields.each do |name, _|
362
+ session.class.related_fields.each do |name, definition|
363
+ next if definition.opts[:default_expiration].nil?
364
+
337
365
  related_key = "#{session.dbkey}:#{name}"
338
366
  pipe.expire(related_key, 3600)
339
367
  end
@@ -367,9 +395,26 @@ class ResilientSession < Familia::Horreum
367
395
  end
368
396
  ```
369
397
 
370
- ## Debugging and Troubleshooting
398
+ ## Error Handling and Validation
399
+
400
+ ### Expiration Value Validation
401
+
402
+ ```ruby
403
+ session = UserSession.new(session_token: 'test')
404
+
405
+ # Invalid expiration type
406
+ session.update_expiration(expiration: "invalid")
407
+ # => Familia::Problem: Default expiration must be a number (String given for UserSession)
371
408
 
372
- ### Debug Expiration Issues
409
+ # Negative expiration
410
+ session.update_expiration(expiration: -1)
411
+ # => Familia::Problem: Default expiration must be non-negative (-1 given for UserSession)
412
+
413
+ # Valid expiration
414
+ session.update_expiration(expiration: 3600) # => true
415
+ ```
416
+
417
+ ### Debug Logging
373
418
 
374
419
  ```ruby
375
420
  # Enable debug logging to see expiration operations
@@ -378,10 +423,12 @@ Familia.debug = true
378
423
  session = UserSession.new(session_token: 'debug_session')
379
424
  session.save
380
425
  session.update_expiration(expiration: 5.minutes)
381
- # Logs will show:
382
- # [update_expiration] Expires session:debug_session in 300.0 seconds
426
+ # Structured logging output:
427
+ # TTL updated operation=expire key=user_session:debug_session ttl_seconds=300.0 class=UserSession identifier=debug_session
383
428
  ```
384
429
 
430
+ ## Debugging and Troubleshooting
431
+
385
432
  ### Common Issues
386
433
 
387
434
  **1. Expiration Not Applied**
@@ -401,23 +448,16 @@ class Customer < Familia::Horreum
401
448
  feature :expiration
402
449
 
403
450
  field :name
404
- list :orders # ❌ Won't cascade without proper relation definition
451
+ list :orders # ❌ Won't cascade - no default_expiration defined
405
452
  end
406
453
 
407
- # ✅ Fix: Ensure relations are properly tracked
454
+ # ✅ Fix: Define default_expiration for fields that should cascade
408
455
  class Customer < Familia::Horreum
409
456
  feature :expiration
457
+ default_expiration 1.day
410
458
 
411
459
  field :name
412
- list :orders
413
-
414
- # Explicitly track relation if needed
415
- def update_expiration(**opts)
416
- super(**opts)
417
-
418
- # Manually cascade to specific fields if needed
419
- orders.expire(opts[:default_expiration] || default_expiration)
420
- end
460
+ list :orders, default_expiration: 12.hours # Will cascade
421
461
  end
422
462
  ```
423
463
 
@@ -439,6 +479,20 @@ class BaseModel < Familia::Horreum
439
479
  end
440
480
  ```
441
481
 
482
+ **4. No-Op Behavior Without Feature**
483
+ ```ruby
484
+ class BasicModel < Familia::Horreum
485
+ # No expiration feature enabled
486
+ field :data
487
+ end
488
+
489
+ basic = BasicModel.new
490
+ basic.update_expiration(expiration: 1.hour) # No-op, returns nil
491
+ basic.ttl # Returns -1
492
+ basic.expires? # Returns false
493
+ basic.expired? # Returns false
494
+ ```
495
+
442
496
  ## Testing TTL Behavior
443
497
 
444
498
  ### RSpec Testing
@@ -466,9 +520,28 @@ RSpec.describe UserSession do
466
520
  expect(ttl).to be <= 600
467
521
  end
468
522
 
469
- it "cascades expiration to related fields" do
523
+ it "provides TTL status methods" do
524
+ session.save
525
+ session.update_expiration(expiration: 5.minutes)
526
+
527
+ expect(session.expires?).to be true
528
+ expect(session.expired?).to be false
529
+ expect(session.expired?(10.minutes)).to be true # Expiring within 10 minutes
530
+ end
531
+
532
+ it "supports expiration extension" do
533
+ session.save
534
+ session.update_expiration(expiration: 5.minutes)
535
+
536
+ initial_ttl = session.ttl
537
+ session.extend_expiration(5.minutes)
538
+
539
+ expect(session.ttl).to be > initial_ttl
540
+ end
541
+
542
+ it "cascades expiration to related fields with default_expiration" do
470
543
  session.save
471
- session.activity_log.push('login') # Assume activity_log is a list
544
+ session.activity_log.push('login') # Assume activity_log has default_expiration
472
545
 
473
546
  session.update_expiration(expiration: 5.minutes)
474
547
 
@@ -506,92 +579,63 @@ class SessionExpirationTest < ActionDispatch::IntegrationTest
506
579
  end
507
580
  ```
508
581
 
509
- ## Best Practices
582
+ ## API Reference
510
583
 
511
- ### 1. Set Appropriate Defaults
584
+ ### Instance Methods
512
585
 
513
- ```ruby
514
- class SessionStore < Familia::Horreum
515
- feature :expiration
586
+ #### `update_expiration(expiration: nil)`
587
+ Sets TTL for the object and cascades to related fields with `default_expiration`.
516
588
 
517
- # Choose TTL based on security requirements
518
- case Rails.env
519
- when 'development'
520
- default_expiration 8.hours # Convenience for debugging
521
- when 'test'
522
- default_expiration 1.minute # Fast cleanup in tests
523
- when 'production'
524
- default_expiration 30.minutes # Security-focused
525
- end
526
- end
527
- ```
589
+ **Parameters:**
590
+ - `expiration` (Numeric, nil) - TTL in seconds. Uses `default_expiration` if nil.
528
591
 
529
- ### 2. Monitor TTL Health
592
+ **Returns:** Boolean indicating success
530
593
 
531
- ```ruby
532
- class TTLHealthCheck
533
- def self.check_session_health
534
- expired_count = 0
535
- total_count = 0
594
+ **Raises:** `Familia::Problem` for invalid expiration values
536
595
 
537
- UserSession.all.each do |session|
538
- total_count += 1
596
+ #### `ttl`
597
+ Get remaining TTL for this object.
539
598
 
540
- ttl = session.ttl
541
- if ttl == -2 # Key doesn't exist
542
- expired_count += 1
543
- elsif ttl < 300 # Less than 5 minutes remaining
544
- # Extend TTL for active sessions
545
- session.update_expiration(expiration: 30.minutes) if session.active?
546
- end
547
- end
599
+ **Returns:**
600
+ - Integer > 0: Seconds remaining
601
+ - -1: No TTL set (persistent)
602
+ - -2: Key doesn't exist
548
603
 
549
- {
550
- total_sessions: total_count,
551
- expired_sessions: expired_count,
552
- expiration_rate: expired_count.to_f / total_count
553
- }
554
- end
555
- end
556
- ```
604
+ #### `expires?`
605
+ Check if object has TTL set.
557
606
 
558
- ### 3. Graceful Degradation
607
+ **Returns:** Boolean - true if TTL is set
559
608
 
560
- ```ruby
561
- class RobustSessionManager
562
- def self.get_or_create_session(session_token)
563
- session = UserSession.find(session_token)
609
+ #### `expired?(threshold = 0)`
610
+ Check if object is expired or expiring soon.
564
611
 
565
- # Check if session exists and hasn't expired
566
- if session&.ttl&.positive?
567
- # Extend TTL on access
568
- session.update_expiration(expiration: 30.minutes)
569
- session
570
- else
571
- # Create new session if old one expired
572
- create_new_session
573
- end
574
- rescue => e
575
- # Fallback: create new session on any error
576
- Rails.logger.warn "Session retrieval failed: #{e.message}"
577
- create_new_session
578
- end
579
- end
580
- ```
612
+ **Parameters:**
613
+ - `threshold` (Numeric) - Consider expired if TTL <= threshold
581
614
 
582
- ### 4. Environment-Specific Configuration
615
+ **Returns:** Boolean - true if expired/expiring
583
616
 
584
- ```ruby
585
- # config/initializers/familia_expiration.rb
586
- Familia.configure do |config|
587
- # Set global default based on environment
588
- config.default_expiration = case Rails.env
589
- when 'development' then 0 # No expiration for debugging
590
- when 'test' then 1.minute # Quick cleanup
591
- when 'production' then 1.hour # Reasonable default
592
- end
593
- end
594
- ```
617
+ #### `extend_expiration(duration)`
618
+ Extend current TTL by additional time.
619
+
620
+ **Parameters:**
621
+ - `duration` (Numeric) - Additional seconds to add
622
+
623
+ **Returns:** Boolean indicating success
624
+
625
+ #### `persist!`
626
+ Remove TTL, making object persistent.
627
+
628
+ **Returns:** Boolean indicating success
629
+
630
+ ### Class Methods
631
+
632
+ #### `default_expiration(num = nil)`
633
+ Get/set class-level default expiration.
634
+
635
+ **Parameters:**
636
+ - `num` (Numeric, nil) - Set expiration if provided
637
+
638
+ **Returns:** Float - current default expiration in seconds
595
639
 
596
640
  ---
597
641
 
@@ -602,4 +646,4 @@ end
602
646
  - **[Feature System Guide](feature-system.md)** - Understanding Familia's feature architecture
603
647
  - **[Implementation Guide](implementation.md)** - Production deployment and configuration patterns
604
648
 
605
- The Expiration feature provides a robust foundation for managing data lifecycle in Familia applications, with flexible configuration options and automatic cascading to related objects.
649
+ The Expiration feature provides a robust foundation for managing data lifecycle in Familia applications, with flexible configuration options, automatic cascading to related objects, and comprehensive TTL monitoring capabilities.