familia 2.0.0.pre17 → 2.0.0.pre19

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.rst +118 -6
  3. data/CLAUDE.md +43 -11
  4. data/Gemfile +2 -2
  5. data/Gemfile.lock +9 -47
  6. data/README.md +52 -0
  7. data/bin/irb +1 -1
  8. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +91 -0
  9. data/changelog.d/20251011_203905_delano_next.rst +30 -0
  10. data/changelog.d/20251011_212633_delano_next.rst +13 -0
  11. data/changelog.d/20251011_221253_delano_next.rst +26 -0
  12. data/docs/guides/core-field-system.md +48 -26
  13. data/docs/guides/feature-expiration.md +18 -18
  14. data/docs/migrating/v2.0.0-pre18.md +58 -0
  15. data/docs/migrating/v2.0.0-pre19.md +197 -0
  16. data/docs/qodo-merge-compliance.md +96 -0
  17. data/examples/datatype_standalone.rb +281 -0
  18. data/lib/familia/base.rb +0 -2
  19. data/lib/familia/connection/behavior.rb +252 -0
  20. data/lib/familia/connection/handlers.rb +95 -0
  21. data/lib/familia/connection/middleware.rb +58 -4
  22. data/lib/familia/connection/operation_core.rb +1 -1
  23. data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +2 -2
  24. data/lib/familia/connection/transaction_core.rb +7 -9
  25. data/lib/familia/connection.rb +2 -1
  26. data/lib/familia/data_type/connection.rb +151 -7
  27. data/lib/familia/data_type/{commands.rb → database_commands.rb} +9 -6
  28. data/lib/familia/data_type/serialization.rb +9 -5
  29. data/lib/familia/data_type/types/hashkey.rb +1 -1
  30. data/lib/familia/data_type.rb +2 -2
  31. data/lib/familia/encryption/encrypted_data.rb +12 -2
  32. data/lib/familia/encryption/manager.rb +11 -4
  33. data/lib/familia/errors.rb +51 -14
  34. data/lib/familia/features/autoloader.rb +3 -1
  35. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +11 -3
  36. data/lib/familia/features/expiration/extensions.rb +8 -10
  37. data/lib/familia/features/expiration.rb +19 -19
  38. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +45 -44
  39. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +151 -65
  40. data/lib/familia/features/relationships/indexing.rb +37 -42
  41. data/lib/familia/features/relationships/indexing_relationship.rb +14 -4
  42. data/lib/familia/features/safe_dump.rb +2 -3
  43. data/lib/familia/field_type.rb +2 -1
  44. data/lib/familia/horreum/connection.rb +11 -35
  45. data/lib/familia/horreum/database_commands.rb +130 -11
  46. data/lib/familia/horreum/definition.rb +8 -38
  47. data/lib/familia/horreum/management.rb +38 -27
  48. data/lib/familia/horreum/persistence.rb +191 -67
  49. data/lib/familia/horreum/serialization.rb +94 -73
  50. data/lib/familia/horreum/utils.rb +0 -8
  51. data/lib/familia/horreum.rb +41 -18
  52. data/lib/familia/identifier_extractor.rb +60 -0
  53. data/lib/familia/logging.rb +268 -112
  54. data/lib/familia/refinements.rb +0 -1
  55. data/lib/familia/settings.rb +7 -7
  56. data/lib/familia/version.rb +1 -1
  57. data/lib/familia.rb +2 -2
  58. data/lib/middleware/{database_middleware.rb → database_logger.rb} +118 -14
  59. data/pr_agent.toml +31 -0
  60. data/pr_compliance_checklist.yaml +45 -0
  61. data/try/edge_cases/empty_identifiers_try.rb +1 -1
  62. data/try/edge_cases/hash_symbolization_try.rb +31 -31
  63. data/try/edge_cases/json_serialization_try.rb +2 -2
  64. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +170 -0
  65. data/try/edge_cases/race_conditions_try.rb +1 -1
  66. data/try/edge_cases/reserved_keywords_try.rb +1 -1
  67. data/try/edge_cases/string_coercion_try.rb +5 -5
  68. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  69. data/try/features/encrypted_fields/aad_protection_try.rb +1 -1
  70. data/try/features/encrypted_fields/concealed_string_core_try.rb +1 -1
  71. data/try/features/encrypted_fields/context_isolation_try.rb +1 -1
  72. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  73. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +1 -1
  74. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +1 -1
  75. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +1 -1
  76. data/try/features/encrypted_fields/error_conditions_try.rb +1 -1
  77. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +1 -1
  78. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  79. data/try/features/encrypted_fields/key_rotation_try.rb +1 -1
  80. data/try/features/encrypted_fields/memory_security_try.rb +1 -1
  81. data/try/features/encrypted_fields/missing_current_key_version_try.rb +1 -1
  82. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  83. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +1 -1
  84. data/try/features/encrypted_fields/thread_safety_try.rb +1 -1
  85. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +1 -1
  86. data/try/{encryption → features/encryption}/config_persistence_try.rb +1 -1
  87. data/try/{encryption/encryption_core_try.rb → features/encryption/core_try.rb} +2 -2
  88. data/try/{encryption → features/encryption}/instance_variable_scope_try.rb +1 -1
  89. data/try/{encryption → features/encryption}/module_loading_try.rb +1 -1
  90. data/try/{encryption → features/encryption}/providers/aes_gcm_provider_try.rb +1 -1
  91. data/try/{encryption → features/encryption}/providers/xchacha20_poly1305_provider_try.rb +1 -1
  92. data/try/{encryption → features/encryption}/roundtrip_validation_try.rb +1 -1
  93. data/try/{encryption → features/encryption}/secure_memory_handling_try.rb +2 -2
  94. data/try/features/expiration/expiration_try.rb +2 -2
  95. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  96. data/try/features/feature_dependencies_try.rb +1 -1
  97. data/try/features/feature_improvements_try.rb +1 -1
  98. data/try/features/object_identifier/object_identifier_integration_try.rb +1 -1
  99. data/try/features/object_identifier/object_identifier_try.rb +1 -1
  100. data/try/features/quantization/quantization_try.rb +1 -1
  101. data/try/features/real_feature_integration_try.rb +17 -14
  102. data/try/features/relationships/indexing_commands_verification_try.rb +8 -3
  103. data/try/features/relationships/indexing_try.rb +34 -5
  104. data/try/features/relationships/participation_commands_verification_spec.rb +1 -1
  105. data/try/features/relationships/participation_commands_verification_try.rb +4 -4
  106. data/try/features/relationships/participation_performance_improvements_try.rb +1 -1
  107. data/try/features/relationships/participation_reverse_index_try.rb +1 -1
  108. data/try/features/relationships/relationships_api_changes_try.rb +5 -5
  109. data/try/features/relationships/relationships_edge_cases_try.rb +3 -3
  110. data/try/features/relationships/relationships_performance_minimal_try.rb +1 -1
  111. data/try/features/relationships/relationships_performance_simple_try.rb +1 -1
  112. data/try/features/relationships/relationships_performance_try.rb +1 -1
  113. data/try/features/relationships/relationships_performance_working_try.rb +1 -1
  114. data/try/features/relationships/relationships_try.rb +1 -1
  115. data/try/features/safe_dump/safe_dump_advanced_try.rb +1 -1
  116. data/try/features/safe_dump/safe_dump_try.rb +1 -1
  117. data/try/features/transient_fields/redacted_string_try.rb +1 -1
  118. data/try/features/transient_fields/refresh_reset_try.rb +1 -1
  119. data/try/features/transient_fields/single_use_redacted_string_try.rb +1 -1
  120. data/try/features/transient_fields/transient_fields_core_try.rb +1 -1
  121. data/try/features/transient_fields/transient_fields_integration_try.rb +1 -1
  122. data/try/{connection → integration/connection}/fiber_context_preservation_try.rb +4 -4
  123. data/try/{connection → integration/connection}/handler_constraints_try.rb +1 -1
  124. data/try/{core → integration/connection}/isolated_dbclient_try.rb +1 -1
  125. data/try/integration/connection/middleware_reconnect_try.rb +87 -0
  126. data/try/{connection → integration/connection}/operation_mode_guards_try.rb +2 -2
  127. data/try/{connection → integration/connection}/pipeline_fallback_integration_try.rb +13 -13
  128. data/try/{core → integration/connection}/pools_try.rb +1 -1
  129. data/try/{connection → integration/connection}/responsibility_chain_tracking_try.rb +1 -1
  130. data/try/{connection → integration/connection}/transaction_fallback_integration_try.rb +1 -1
  131. data/try/{connection → integration/connection}/transaction_mode_permissive_try.rb +1 -1
  132. data/try/{connection → integration/connection}/transaction_mode_strict_try.rb +1 -1
  133. data/try/{connection → integration/connection}/transaction_mode_warn_try.rb +1 -1
  134. data/try/{connection → integration/connection}/transaction_modes_try.rb +1 -1
  135. data/try/{core → integration}/conventional_inheritance_try.rb +1 -1
  136. data/try/{core → integration}/create_method_try.rb +23 -23
  137. data/try/integration/cross_component_try.rb +1 -1
  138. data/try/integration/data_types/datatype_pipelines_try.rb +104 -0
  139. data/try/integration/data_types/datatype_transactions_try.rb +247 -0
  140. data/try/{core → integration}/database_consistency_try.rb +11 -8
  141. data/try/{core → integration}/familia_extended_try.rb +1 -1
  142. data/try/{core → integration}/familia_members_methods_try.rb +1 -1
  143. data/try/{models → integration/models}/customer_safe_dump_try.rb +6 -2
  144. data/try/{models → integration/models}/customer_try.rb +1 -1
  145. data/try/{models → integration/models}/datatype_base_try.rb +1 -1
  146. data/try/{models → integration/models}/familia_object_try.rb +2 -2
  147. data/try/{core → integration}/persistence_operations_try.rb +163 -11
  148. data/try/integration/relationships_persistence_round_trip_try.rb +441 -0
  149. data/try/{configuration → integration}/scenarios_try.rb +1 -1
  150. data/try/{core → integration}/secure_identifier_try.rb +1 -1
  151. data/try/{core → integration}/verifiable_identifier_try.rb +1 -1
  152. data/try/performance/benchmarks_try.rb +2 -2
  153. data/try/support/benchmarks/deserialization_benchmark.rb +180 -0
  154. data/try/support/benchmarks/deserialization_correctness_test.rb +237 -0
  155. data/try/{helpers → support/helpers}/test_helpers.rb +12 -3
  156. data/try/{core → unit/core}/autoloader_try.rb +1 -1
  157. data/try/{core → unit/core}/base_enhancements_try.rb +1 -9
  158. data/try/{core → unit/core}/connection_try.rb +1 -1
  159. data/try/{core → unit/core}/errors_try.rb +1 -1
  160. data/try/{core → unit/core}/extensions_try.rb +1 -1
  161. data/try/unit/core/familia_logger_try.rb +110 -0
  162. data/try/{core → unit/core}/familia_try.rb +1 -1
  163. data/try/{core → unit/core}/middleware_try.rb +41 -1
  164. data/try/{core → unit/core}/settings_try.rb +1 -1
  165. data/try/{core → unit/core}/time_utils_try.rb +1 -1
  166. data/try/{core → unit/core}/tools_try.rb +1 -1
  167. data/try/{core → unit/core}/utils_try.rb +17 -14
  168. data/try/{data_types → unit/data_types}/boolean_try.rb +2 -2
  169. data/try/{data_types → unit/data_types}/counter_try.rb +1 -1
  170. data/try/{data_types → unit/data_types}/datatype_base_try.rb +1 -1
  171. data/try/{data_types → unit/data_types}/hash_try.rb +1 -1
  172. data/try/{data_types → unit/data_types}/list_try.rb +1 -1
  173. data/try/{data_types → unit/data_types}/lock_try.rb +1 -1
  174. data/try/{data_types → unit/data_types}/sorted_set_try.rb +1 -1
  175. data/try/{data_types → unit/data_types}/sorted_set_zadd_options_try.rb +1 -1
  176. data/try/{data_types → unit/data_types}/string_try.rb +2 -2
  177. data/try/{data_types → unit/data_types}/unsortedset_try.rb +1 -1
  178. data/try/{horreum → unit/horreum}/auto_indexing_on_save_try.rb +33 -17
  179. data/try/unit/horreum/automatic_index_validation_try.rb +253 -0
  180. data/try/{horreum → unit/horreum}/base_try.rb +4 -4
  181. data/try/{horreum → unit/horreum}/class_methods_try.rb +3 -3
  182. data/try/{horreum → unit/horreum}/commands_try.rb +1 -1
  183. data/try/{horreum → unit/horreum}/defensive_initialization_try.rb +1 -1
  184. data/try/{horreum → unit/horreum}/destroy_related_fields_cleanup_try.rb +1 -1
  185. data/try/{horreum → unit/horreum}/enhanced_conflict_handling_try.rb +1 -1
  186. data/try/{horreum → unit/horreum}/field_categories_try.rb +27 -18
  187. data/try/{horreum → unit/horreum}/field_definition_try.rb +1 -1
  188. data/try/{horreum → unit/horreum}/initialization_try.rb +3 -3
  189. data/try/unit/horreum/json_type_preservation_try.rb +248 -0
  190. data/try/{horreum → unit/horreum}/relations_try.rb +5 -5
  191. data/try/{horreum → unit/horreum}/serialization_persistent_fields_try.rb +24 -18
  192. data/try/{horreum → unit/horreum}/serialization_try.rb +6 -6
  193. data/try/{horreum → unit/horreum}/settings_try.rb +1 -1
  194. data/try/unit/horreum/unique_index_edge_cases_try.rb +376 -0
  195. data/try/unit/horreum/unique_index_guard_validation_try.rb +281 -0
  196. data/try/{refinements → unit/refinements}/dear_json_array_methods_try.rb +1 -1
  197. data/try/{refinements → unit/refinements}/dear_json_hash_methods_try.rb +1 -1
  198. data/try/{refinements → unit/refinements}/time_literals_numeric_methods_try.rb +1 -1
  199. data/try/{refinements → unit/refinements}/time_literals_string_methods_try.rb +1 -1
  200. metadata +147 -126
  201. data/lib/familia/distinguisher.rb +0 -85
  202. data/lib/familia/refinements/logger_trace.rb +0 -60
  203. data/try/refinements/logger_trace_methods_try.rb +0 -44
  204. /data/try/{debugging → support/debugging}/README.md +0 -0
  205. /data/try/{debugging → support/debugging}/cache_behavior_tracer.rb +0 -0
  206. /data/try/{debugging → support/debugging}/debug_aad_process.rb +0 -0
  207. /data/try/{debugging → support/debugging}/debug_concealed_internal.rb +0 -0
  208. /data/try/{debugging → support/debugging}/debug_concealed_reveal.rb +0 -0
  209. /data/try/{debugging → support/debugging}/debug_context_aad.rb +0 -0
  210. /data/try/{debugging → support/debugging}/debug_context_simple.rb +0 -0
  211. /data/try/{debugging → support/debugging}/debug_cross_context.rb +0 -0
  212. /data/try/{debugging → support/debugging}/debug_database_load.rb +0 -0
  213. /data/try/{debugging → support/debugging}/debug_encrypted_json_check.rb +0 -0
  214. /data/try/{debugging → support/debugging}/debug_encrypted_json_step_by_step.rb +0 -0
  215. /data/try/{debugging → support/debugging}/debug_exists_lifecycle.rb +0 -0
  216. /data/try/{debugging → support/debugging}/debug_field_decrypt.rb +0 -0
  217. /data/try/{debugging → support/debugging}/debug_fresh_cross_context.rb +0 -0
  218. /data/try/{debugging → support/debugging}/debug_load_path.rb +0 -0
  219. /data/try/{debugging → support/debugging}/debug_method_definition.rb +0 -0
  220. /data/try/{debugging → support/debugging}/debug_method_resolution.rb +0 -0
  221. /data/try/{debugging → support/debugging}/debug_minimal.rb +0 -0
  222. /data/try/{debugging → support/debugging}/debug_provider.rb +0 -0
  223. /data/try/{debugging → support/debugging}/debug_secure_behavior.rb +0 -0
  224. /data/try/{debugging → support/debugging}/debug_string_class.rb +0 -0
  225. /data/try/{debugging → support/debugging}/debug_test.rb +0 -0
  226. /data/try/{debugging → support/debugging}/debug_test_design.rb +0 -0
  227. /data/try/{debugging → support/debugging}/encryption_method_tracer.rb +0 -0
  228. /data/try/{debugging → support/debugging}/provider_diagnostics.rb +0 -0
  229. /data/try/{helpers → support/helpers}/test_cleanup.rb +0 -0
  230. /data/try/{memory → support/memory}/memory_basic_test.rb +0 -0
  231. /data/try/{memory → support/memory}/memory_detailed_test.rb +0 -0
  232. /data/try/{memory → support/memory}/memory_docker_ruby_dump.sh +0 -0
  233. /data/try/{memory → support/memory}/memory_search_for_string.rb +0 -0
  234. /data/try/{memory → support/memory}/test_actual_redactedstring_protection.rb +0 -0
  235. /data/try/{prototypes → support/prototypes}/atomic_saves_v1_context_proxy.rb +0 -0
  236. /data/try/{prototypes → support/prototypes}/atomic_saves_v2_connection_switching.rb +0 -0
  237. /data/try/{prototypes → support/prototypes}/atomic_saves_v3_connection_pool.rb +0 -0
  238. /data/try/{prototypes → support/prototypes}/atomic_saves_v4.rb +0 -0
  239. /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v2_connection_switching_helpers.rb +0 -0
  240. /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
  241. /data/try/{prototypes → support/prototypes}/pooling/README.md +0 -0
  242. /data/try/{prototypes → support/prototypes}/pooling/configurable_stress_test.rb +0 -0
  243. /data/try/{prototypes → support/prototypes}/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
  244. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_metrics.rb +0 -0
  245. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_stress_test.rb +0 -0
  246. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_threading_models.rb +0 -0
  247. /data/try/{prototypes → support/prototypes}/pooling/lib/visualize_stress_results.rb +0 -0
  248. /data/try/{prototypes → support/prototypes}/pooling/pool_siege.rb +0 -0
  249. /data/try/{prototypes → support/prototypes}/pooling/run_stress_tests.rb +0 -0
@@ -50,17 +50,20 @@ customer.email = "admin@acme.com" # Custom method name
50
50
  customer.name!("Updated Corp") # Fast writer (immediate DB persistence)
51
51
  ```
52
52
 
53
- ### Field Categories
53
+ ### Special Field Types
54
54
 
55
- Fields can be categorized for special processing by features:
55
+ Use dedicated field methods provided by features for special field behaviors:
56
56
 
57
57
  ```ruby
58
58
  class Document < Familia::Horreum
59
- field :title # Regular field
60
- field :content, category: :encrypted # Will be processed by encrypted_fields feature
61
- field :api_key, category: :transient # Non-persistent field
62
- field :tags, category: :indexed # Custom category for indexing
63
- field :metadata, category: :json # Custom JSON serialization
59
+ feature :encrypted_fields
60
+ feature :transient_fields
61
+
62
+ field :title # Regular persistent field
63
+ encrypted_field :content # Encrypted storage
64
+ transient_field :api_key # Non-persistent (memory only)
65
+ field :tags # Regular field
66
+ field :metadata # Regular field
64
67
  end
65
68
  ```
66
69
 
@@ -285,27 +288,28 @@ order.priority_urgent? # => false
285
288
 
286
289
  ```ruby
287
290
  class Product < Familia::Horreum
288
- field :name, category: :searchable
289
- field :price, category: :numeric
290
- field :description, category: :text
291
- field :secret_key, category: :encrypted
291
+ feature :transient_fields
292
+
293
+ field :name
294
+ field :price
295
+ field :description
292
296
  transient_field :temp_data
293
297
  end
294
298
 
295
299
  # Get all field names
296
300
  Product.fields
297
- # => [:name, :price, :description, :secret_key, :temp_data]
301
+ # => [:name, :price, :description, :temp_data]
298
302
 
299
303
  # Get field types registry
300
304
  Product.field_types
301
305
  # => { name: #<FieldType...>, price: #<FieldType...>, ... }
302
306
 
303
- # Get fields by category
304
- Product.fields.select { |f| Product.field_types[f].category == :searchable }
305
- # => [:name]
307
+ # Get fields by category (read-only introspection)
308
+ Product.fields.select { |f| Product.field_types[f].category == :transient }
309
+ # => [:temp_data]
306
310
 
307
311
  # Get persistent vs transient fields
308
- Product.persistent_fields # => [:name, :price, :description, :secret_key]
312
+ Product.persistent_fields # => [:name, :price, :description]
309
313
  Product.transient_fields # => [:temp_data]
310
314
 
311
315
  # Field method mapping (for backward compatibility)
@@ -313,15 +317,24 @@ Product.field_method_map
313
317
  # => { name: :name, price: :price, secret_key: :secret_key, temp_data: :temp_data }
314
318
  ```
315
319
 
316
- ### Field Categories for Feature Processing
320
+ ### Using Field Type Category for Introspection
321
+
322
+ The `category` method on FieldType provides read-only metadata for introspection:
317
323
 
318
324
  ```ruby
319
- # Features can process fields by category
325
+ # Custom field type with category metadata
326
+ class SearchableFieldType < Familia::FieldType
327
+ def category
328
+ :searchable
329
+ end
330
+ end
331
+
332
+ # Features can process fields by inspecting their category
320
333
  module SearchableFieldsFeature
321
334
  def self.included(base)
322
335
  base.extend ClassMethods
323
336
 
324
- # Process all searchable fields
337
+ # Find all searchable fields by inspecting field type category
325
338
  searchable_fields = base.fields.select do |field|
326
339
  base.field_types[field].category == :searchable
327
340
  end
@@ -348,11 +361,17 @@ module SearchableFieldsFeature
348
361
  end
349
362
 
350
363
  class Product < Familia::Horreum
351
- feature :searchable_fields # Processes all :searchable category fields
364
+ feature :searchable_fields
352
365
 
353
- field :name, category: :searchable
354
- field :description, category: :searchable
355
- field :internal_id, category: :system
366
+ # Use custom field type with searchable category
367
+ def self.searchable_field(name, **options)
368
+ field_type = SearchableFieldType.new(name, **options)
369
+ register_field_type(field_type)
370
+ end
371
+
372
+ searchable_field :name
373
+ searchable_field :description
374
+ field :internal_id
356
375
  end
357
376
 
358
377
  # Auto-generated search methods available
@@ -733,10 +752,13 @@ end
733
752
  ### 1. Choose Appropriate Field Types
734
753
 
735
754
  ```ruby
736
- # Use built-in field types when possible
755
+ # Use dedicated field methods provided by features
737
756
  class User < Familia::Horreum
738
- field :name # Simple string field
739
- field :metadata, category: :json # For complex data
757
+ feature :transient_fields
758
+ feature :encrypted_fields
759
+
760
+ field :name # Simple persistent field
761
+ field :metadata # For complex data
740
762
  transient_field :temp_token # For runtime-only data
741
763
  encrypted_field :api_key # For sensitive data
742
764
  end
@@ -66,7 +66,7 @@ session.default_expiration # => 900.0
66
66
  session.update_expiration # Uses instance expiration (15 minutes)
67
67
 
68
68
  # Or specify expiration inline
69
- session.update_expiration(default_expiration: 5.minutes)
69
+ session.update_expiration(expiration: 5.minutes)
70
70
  ```
71
71
 
72
72
  ## Advanced Usage
@@ -112,7 +112,7 @@ customer = Customer.new(customer_id: 'cust_123')
112
112
  customer.save
113
113
 
114
114
  # This will set TTL on the main object AND all related fields
115
- customer.update_expiration(default_expiration: 12.hours)
115
+ customer.update_expiration(expiration: 12.hours)
116
116
  # Sets expiration on:
117
117
  # - customer:cust_123 (main hash)
118
118
  # - customer:cust_123:recent_orders (list)
@@ -137,9 +137,9 @@ class AnalyticsEvent < Familia::Horreum
137
137
  save
138
138
 
139
139
  if should_expire?
140
- update_expiration(default_expiration: 1.hour)
140
+ update_expiration(expiration: 1.hour)
141
141
  else
142
- update_expiration(default_expiration: 30.days)
142
+ update_expiration(expiration: 30.days)
143
143
  end
144
144
  end
145
145
  end
@@ -200,7 +200,7 @@ class SessionCleanupJob
200
200
  # Extend expiration for active sessions
201
201
  UserSession.all.each do |session|
202
202
  if session.recently_active?
203
- session.update_expiration(default_expiration: 30.minutes)
203
+ session.update_expiration(expiration: 30.minutes)
204
204
  end
205
205
  end
206
206
  end
@@ -225,7 +225,7 @@ class SessionExpirationMiddleware
225
225
  session = UserSession.find(session_token)
226
226
 
227
227
  # Extend session TTL on each request
228
- session&.update_expiration(default_expiration: 30.minutes)
228
+ session&.update_expiration(expiration: 30.minutes)
229
229
  end
230
230
 
231
231
  @app.call(env)
@@ -268,14 +268,14 @@ end
268
268
  class SessionManager
269
269
  def self.extend_all_sessions(new_ttl)
270
270
  UserSession.all.each do |session|
271
- session.update_expiration(default_expiration: new_ttl)
271
+ session.update_expiration(expiration: new_ttl)
272
272
  end
273
273
  end
274
274
 
275
275
  def self.expire_inactive_sessions
276
276
  UserSession.all.select(&:inactive?).each do |session|
277
277
  # Set very short TTL for inactive sessions
278
- session.update_expiration(default_expiration: 5.minutes)
278
+ session.update_expiration(expiration: 5.minutes)
279
279
  end
280
280
  end
281
281
 
@@ -306,7 +306,7 @@ class DataRetentionService
306
306
  model_class = data_type.to_s.pascalize.constantize
307
307
 
308
308
  model_class.all.each do |record|
309
- record.update_expiration(default_expiration: ttl)
309
+ record.update_expiration(expiration: ttl)
310
310
  end
311
311
  end
312
312
  end
@@ -323,7 +323,7 @@ DataRetentionService.apply_retention_policies
323
323
  ```ruby
324
324
  # ❌ Inefficient: Multiple round trips
325
325
  sessions.each do |session|
326
- session.update_expiration(default_expiration: 1.hour)
326
+ session.update_expiration(expiration: 1.hour)
327
327
  end
328
328
 
329
329
  # ✅ Efficient: Batch operations
@@ -357,7 +357,7 @@ class ResilientSession < Familia::Horreum
357
357
  return unless exists?
358
358
 
359
359
  begin
360
- update_expiration(default_expiration: new_ttl)
360
+ update_expiration(expiration: new_ttl)
361
361
  rescue => e
362
362
  # Log error but don't crash the application
363
363
  Familia.logger.warn "Failed to update expiration for #{dbkey}: #{e.message}"
@@ -377,7 +377,7 @@ Familia.debug = true
377
377
 
378
378
  session = UserSession.new(session_token: 'debug_session')
379
379
  session.save
380
- session.update_expiration(default_expiration: 5.minutes)
380
+ session.update_expiration(expiration: 5.minutes)
381
381
  # Logs will show:
382
382
  # [update_expiration] Expires session:debug_session in 300.0 seconds
383
383
  ```
@@ -388,11 +388,11 @@ session.update_expiration(default_expiration: 5.minutes)
388
388
  ```ruby
389
389
  session = UserSession.new
390
390
  # ❌ Won't work - object must be saved first
391
- session.update_expiration(default_expiration: 1.hour)
391
+ session.update_expiration(expiration: 1.hour)
392
392
 
393
393
  # ✅ Correct - save first, then expire
394
394
  session.save
395
- session.update_expiration(default_expiration: 1.hour)
395
+ session.update_expiration(expiration: 1.hour)
396
396
  ```
397
397
 
398
398
  **2. Related Fields Not Expiring**
@@ -459,7 +459,7 @@ RSpec.describe UserSession do
459
459
 
460
460
  it "applies TTL to database key" do
461
461
  session.save
462
- session.update_expiration(default_expiration: 10.minutes)
462
+ session.update_expiration(expiration: 10.minutes)
463
463
 
464
464
  ttl = session.ttl
465
465
  expect(ttl).to be > 500 # Should be close to 600 seconds
@@ -470,7 +470,7 @@ RSpec.describe UserSession do
470
470
  session.save
471
471
  session.activity_log.push('login') # Assume activity_log is a list
472
472
 
473
- session.update_expiration(default_expiration: 5.minutes)
473
+ session.update_expiration(expiration: 5.minutes)
474
474
 
475
475
  # Both main object and related fields should have TTL
476
476
  expect(session.ttl).to be > 250
@@ -542,7 +542,7 @@ class TTLHealthCheck
542
542
  expired_count += 1
543
543
  elsif ttl < 300 # Less than 5 minutes remaining
544
544
  # Extend TTL for active sessions
545
- session.update_expiration(default_expiration: 30.minutes) if session.active?
545
+ session.update_expiration(expiration: 30.minutes) if session.active?
546
546
  end
547
547
  end
548
548
 
@@ -565,7 +565,7 @@ class RobustSessionManager
565
565
  # Check if session exists and hasn't expired
566
566
  if session&.ttl&.positive?
567
567
  # Extend TTL on access
568
- session.update_expiration(default_expiration: 30.minutes)
568
+ session.update_expiration(expiration: 30.minutes)
569
569
  session
570
570
  else
571
571
  # Create new session if old one expired
@@ -0,0 +1,58 @@
1
+ # Migrating Guide: v2.0.0-pre18
2
+
3
+ This version completes the JSON serialization implementation by removing the string-as-is optimization and fixes encrypted field visibility in serialization.
4
+
5
+ ## JSON Serialization for All Types
6
+
7
+ **What Changed:**
8
+
9
+ All field values (including strings) are now JSON-encoded during storage for consistent type preservation.
10
+
11
+ **Storage Format:**
12
+
13
+ ```ruby
14
+ # Before (v2.0.0-pre14):
15
+ HGET user:123 name
16
+ "John Doe" # Plain string
17
+
18
+ # After (v2.0.0-pre18):
19
+ HGET user:123 name
20
+ "\"John Doe\"" # JSON-encoded string
21
+ ```
22
+
23
+ **Migration:**
24
+
25
+ No migration needed. The deserializer automatically handles:
26
+ - **New format**: `"\"value\""` → `"value"`
27
+ - **Legacy format**: `"value"` → `"value"`
28
+
29
+ **Why This Matters:**
30
+
31
+ Prevents data corruption for edge cases:
32
+ - Badge `"007"` stays `"007"` (not converted to integer `7`)
33
+ - String `"true"` stays `"true"` (not converted to boolean)
34
+ - String `"null"` stays `"null"` (not converted to `nil`)
35
+
36
+ ## Encrypted Field Security Fix
37
+
38
+ **What Changed:**
39
+
40
+ Fields defined with `category: :encrypted` now correctly exclude encrypted data from `to_h()` output.
41
+
42
+ **Before:**
43
+ ```ruby
44
+ field :secret, category: :encrypted
45
+ user.to_h # => {"id" => "123", "secret" => {...}} # ❌ Exposed!
46
+ ```
47
+
48
+ **After:**
49
+ ```ruby
50
+ field :secret, category: :encrypted
51
+ user.to_h # => {"id" => "123"} # ✅ Secure
52
+ ```
53
+
54
+ Both `encrypted_field` and `field :name, category: :encrypted` now behave identically for security.
55
+
56
+ **Migration:**
57
+
58
+ No code changes needed. Review any code relying on encrypted fields appearing in `to_h()` output.
@@ -0,0 +1,197 @@
1
+ # Migrating Guide: v2.0.0-pre19
2
+
3
+ This version introduces significant improvements to Familia's database operations, making them more atomic, reliable, and consistent with Rails conventions. The changes include enhanced error handling, optimistic locking support, and breaking API changes.
4
+
5
+ ## Breaking Changes
6
+
7
+ ### Management.create → create!
8
+
9
+ **What Changed:**
10
+
11
+ The `create` class method has been renamed to `create!` to follow Rails conventions and indicate that it raises exceptions on failure.
12
+
13
+ **Before:**
14
+ ```ruby
15
+ user = User.create(email: "test@example.com")
16
+ ```
17
+
18
+ **After:**
19
+ ```ruby
20
+ user = User.create!(email: "test@example.com")
21
+ ```
22
+
23
+ **Why This Matters:**
24
+
25
+ - Follows Rails naming conventions where `!` methods raise exceptions
26
+ - Makes it clear that `CreationError` will be raised if object already exists
27
+ - Prevents silent failures in object creation
28
+
29
+ ### save_if_not_exists → save_if_not_exists!
30
+
31
+ **What Changed:**
32
+
33
+ The `save_if_not_exists` method has been renamed to `save_if_not_exists!` and now includes optimistic locking with automatic retry logic.
34
+
35
+ **Before:**
36
+ ```ruby
37
+ success = user.save_if_not_exists
38
+ ```
39
+
40
+ **After:**
41
+ ```ruby
42
+ success = user.save_if_not_exists!
43
+ ```
44
+
45
+ **Behavior Changes:**
46
+ - Now uses Redis WATCH/MULTI/EXEC for optimistic locking
47
+ - Automatically retries up to 3 times on `OptimisticLockError`
48
+ - Raises `RecordExistsError` if object already exists
49
+ - More atomic and thread-safe
50
+
51
+ ## Enhanced Error Handling
52
+
53
+ ### New Error Hierarchy
54
+
55
+ **What's New:**
56
+
57
+ A structured error hierarchy provides better error categorization:
58
+
59
+ ```ruby
60
+ Familia::Problem # Base class
61
+ ├── Familia::PersistenceError # Redis/database errors
62
+ │ ├── Familia::NonUniqueKey
63
+ │ ├── Familia::OptimisticLockError
64
+ │ ├── Familia::OperationModeError
65
+ │ ├── Familia::NoConnectionAvailable
66
+ │ ├── Familia::NotFound
67
+ │ └── Familia::NotConnected
68
+ └── Familia::HorreumError # Model-related errors
69
+ ├── Familia::CreationError
70
+ ├── Familia::NoIdentifier
71
+ ├── Familia::FieldTypeError
72
+ ├── Familia::AutoloadError
73
+ ├── Familia::SerializerError
74
+ ├── Familia::UnknownFieldError
75
+ └── Familia::NotDistinguishableError
76
+ ```
77
+
78
+ **Migration:**
79
+
80
+ Update exception handling to use specific error classes:
81
+
82
+ ```ruby
83
+ # Before
84
+ rescue Familia::Problem => e
85
+ # Handle all errors
86
+
87
+ # After - More granular handling
88
+ rescue Familia::CreationError => e
89
+ # Handle creation failures specifically
90
+ rescue Familia::OptimisticLockError => e
91
+ # Handle concurrent modification
92
+ rescue Familia::PersistenceError => e
93
+ # Handle database-related errors
94
+ ```
95
+
96
+ ### New Exception Types
97
+
98
+ - **`CreationError`**: Raised when object creation fails (replaces generic errors)
99
+ - **`OptimisticLockError`**: Raised when WATCH fails due to concurrent modification
100
+ - **`UnknownFieldError`**: Raised when referencing non-existent fields
101
+
102
+ ## Database Operation Improvements
103
+
104
+ ### Atomic Save Operations
105
+
106
+ **What Changed:**
107
+
108
+ The `save` method now uses a single Redis transaction for complete atomicity:
109
+
110
+ ```ruby
111
+ # All operations now happen atomically:
112
+ # 1. Save all fields (HMSET)
113
+ # 2. Set expiration (EXPIRE)
114
+ # 3. Update indexes
115
+ # 4. Add to instances collection
116
+ ```
117
+
118
+ **Benefits:**
119
+ - Eliminates race conditions during save operations
120
+ - Ensures data consistency across related operations
121
+ - Better performance with fewer round trips
122
+
123
+ ### Enhanced Timestamp Precision
124
+
125
+ **What Changed:**
126
+
127
+ Created/updated timestamps now use float values instead of integers for higher precision:
128
+
129
+ **Before:**
130
+ ```ruby
131
+ user.created # => 1697234567 (integer seconds)
132
+ ```
133
+
134
+ **After:**
135
+ ```ruby
136
+ user.created # => 1697234567.123 (float with milliseconds)
137
+ ```
138
+
139
+ **Migration:**
140
+
141
+ No code changes needed. Existing integer timestamps continue to work.
142
+
143
+ ## Redis Command Enhancements
144
+
145
+ ### New Commands Available
146
+
147
+ Added support for optimistic locking commands:
148
+ - `watch(key)` - Watch key for changes
149
+ - `unwatch()` - Remove all watches
150
+ - `discard()` - Discard queued commands
151
+
152
+ ### Improved Command Logging
153
+
154
+ Database command logging now includes:
155
+ - Structured format for better readability
156
+ - Pipelined operation tracking
157
+ - Transaction boundary markers
158
+ - Command timing information
159
+
160
+ ## Terminology Updates
161
+
162
+ **What Changed:**
163
+
164
+ Standardized on "pipelined" terminology throughout (previously mixed "pipeline"/"pipelined").
165
+
166
+ **Files Affected:**
167
+ - Method names now consistently use "pipelined"
168
+ - Documentation updated to match Redis terminology
169
+ - Log messages standardized
170
+
171
+ **Migration:**
172
+
173
+ No code changes needed - this was an internal consistency improvement.
174
+
175
+ ## Recommended Actions
176
+
177
+ 1. **Update method calls:**
178
+ ```ruby
179
+ # Replace all instances
180
+ Model.create(...) → Model.create!(...)
181
+ obj.save_if_not_exists → obj.save_if_not_exists!
182
+ ```
183
+
184
+ 2. **Review error handling:**
185
+ ```ruby
186
+ # Consider more specific error handling
187
+ rescue Familia::CreationError
188
+ rescue Familia::OptimisticLockError
189
+ ```
190
+
191
+ 3. **Test concurrent operations:**
192
+ - The new optimistic locking provides better concurrency handling
193
+ - Verify your application handles `OptimisticLockError` appropriately
194
+
195
+ 4. **Review logging:**
196
+ - Enhanced database command logging may affect log volume
197
+ - Adjust log levels if needed for production environments
@@ -0,0 +1,96 @@
1
+ # Qodo Merge Compliance Configuration
2
+
3
+ This document describes the Qodo Merge (formerly PR-Agent) configuration for the Familia project.
4
+
5
+ ## Overview
6
+
7
+ Qodo Merge provides automated PR analysis, code reviews, and compliance checks. Our configuration enables two key compliance features:
8
+
9
+ 1. **Codebase Duplication Compliance** - Uses RAG (Retrieval-Augmented Generation) to check for duplicate code across related repositories
10
+ 2. **Custom Compliance** - Project-specific rules tailored to Familia's development practices
11
+
12
+ ## Configuration Files
13
+
14
+ ### pr_agent.toml
15
+
16
+ The main Qodo Merge configuration file located in the repository root. It includes:
17
+
18
+ - **Response Language**: Set to English for consistency
19
+ - **RAG Context Enrichment**: Enabled with related repositories (`delano/familia`, `delano/tryouts`, `delano/otto`)
20
+ - **Custom Compliance Path**: References our custom compliance checklist
21
+ - **Ignore Rules**: Excludes generated files and build artifacts from analysis
22
+
23
+ ### pr_compliance_checklist.yaml
24
+
25
+ Custom compliance rules specific to Familia development:
26
+
27
+ #### ErrorHandling
28
+ All external API calls and database operations must have proper error handling with try-catch blocks or appropriate error handling mechanisms.
29
+
30
+ #### TestCoverage
31
+ New features must include tests using the Tryouts framework. Test files should be in the `try/` directory following the `*_try.rb` or `*.try.rb` naming convention.
32
+
33
+ #### ChangelogFragment
34
+ User-facing changes must include a changelog fragment in the `changelog.d/` directory following RST format, or provide explicit justification for omission.
35
+
36
+ #### DocumentationUpdates
37
+ API changes must be reflected in documentation, including YARD comments for new public methods or updates to the `docs/` directory.
38
+
39
+ #### BackwardCompatibility
40
+ Changes must maintain backward compatibility or document breaking changes in migration guides with deprecation warnings.
41
+
42
+ #### ThreadSafety
43
+ Code handling shared state must be thread-safe with proper synchronization or clear documentation of thread-safety assumptions.
44
+
45
+ #### DatabaseKeyNaming
46
+ Database key generation must follow Familia conventions:
47
+ - Use the configured `delim` separator (default `:`)
48
+ - Avoid reserved keywords: `ttl`, `db`, `valkey`, `redis`
49
+ - Handle empty identifiers to prevent stack overflow
50
+
51
+ ## Interactive Commands
52
+
53
+ Team members can trigger on-demand Qodo Merge analysis in PR comments:
54
+
55
+ - `/analyze --review` - Run code review
56
+ - `/analyze --test` - Generate test suggestions
57
+ - `/improve` - Get improvement suggestions
58
+ - `/ask` - Ask questions about the PR
59
+
60
+ ## Compliance Status
61
+
62
+ In PR comments from `@qodo-merge-pro`, you'll see:
63
+
64
+ - 🟢 Green circle - Compliance check passed
65
+ - 🔴 Red circle - Compliance check failed with details
66
+ - ⚪ White circle - Compliance check not configured (should not appear with proper configuration)
67
+
68
+ ## References
69
+
70
+ - [Qodo Merge Configuration Options](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/)
71
+ - [RAG Context Enrichment Guide](https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/)
72
+ - [Compliance Guide](https://qodo-merge-docs.qodo.ai/tools/compliance/)
73
+ - [Best Practices](https://docs.qodo.ai/qodo-documentation/qodo-merge/features/best-practices)
74
+
75
+ ## Maintenance
76
+
77
+ ### Updating Compliance Rules
78
+
79
+ To add or modify compliance rules:
80
+
81
+ 1. Edit `pr_compliance_checklist.yaml`
82
+ 2. Ensure YAML syntax is valid: `ruby -r yaml -e "YAML.load_file('pr_compliance_checklist.yaml')"`
83
+ 3. Commit changes - they take effect immediately on new PRs
84
+
85
+ ### Updating Ignore Rules
86
+
87
+ To exclude additional files from analysis:
88
+
89
+ 1. Edit the `[ignore]` section in `pr_agent.toml`
90
+ 2. Use glob patterns to match file paths
91
+ 3. Test with new PRs to verify exclusions work as expected
92
+
93
+ ## Future Improvements (Optional)
94
+
95
+ - **Centralized Configuration**: Create a `pr-agent-settings` repository with `metadata.yaml` to share configuration across all repos
96
+ - **Wiki Configuration**: Enable repo wiki and create `.pr_agent.toml` page (wiki config takes precedence over local files)