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.
- checksums.yaml +4 -4
- data/.github/workflows/claude-code-review.yml +4 -9
- data/.github/workflows/code-smells.yml +64 -3
- data/.pre-commit-config.yaml +8 -6
- data/.reek.yml +10 -9
- data/.rubocop.yml +4 -0
- data/.talismanrc +5 -1
- data/CHANGELOG.rst +220 -112
- data/CLAUDE.md +28 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +20 -17
- data/bin/try +16 -0
- data/bin/tryouts +16 -0
- data/docs/1106-participates_in-bidirectional-solution.md +129 -0
- data/docs/guides/encryption.md +486 -0
- data/docs/guides/feature-encrypted-fields.md +123 -7
- data/docs/guides/feature-expiration.md +161 -117
- data/docs/guides/feature-external-identifiers.md +415 -443
- data/docs/guides/feature-object-identifiers.md +400 -269
- data/docs/guides/feature-quantization.md +120 -6
- data/docs/guides/feature-relationships-indexing.md +318 -0
- data/docs/guides/feature-relationships-methods.md +146 -604
- data/docs/guides/feature-relationships-participation.md +263 -0
- data/docs/guides/feature-relationships.md +118 -136
- data/docs/guides/feature-system-devs.md +176 -693
- data/docs/guides/feature-system.md +119 -6
- data/docs/guides/feature-transient-fields.md +81 -0
- data/docs/guides/field-system.md +778 -0
- data/docs/guides/index.md +32 -15
- data/docs/guides/logging.md +187 -0
- data/docs/guides/optimized-loading.md +674 -0
- data/docs/guides/thread-safety-monitoring.md +61 -0
- data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
- data/docs/migrating/v2.0.0-pre22.md +241 -0
- data/docs/overview.md +7 -9
- data/docs/reference/api-technical.md +267 -320
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
- data/examples/autoloader/mega_customer.rb +2 -0
- data/examples/datatype_standalone.rb +4 -3
- data/examples/encrypted_fields.rb +2 -1
- data/examples/json_usage_patterns.rb +2 -0
- data/examples/relationships.rb +3 -0
- data/examples/safe_dump.rb +2 -1
- data/examples/sampling_demo.rb +53 -0
- data/examples/single_connection_transaction_confusions.rb +2 -1
- data/familia.gemspec +2 -1
- data/lib/familia/base.rb +2 -0
- data/lib/familia/connection/behavior.rb +2 -0
- data/lib/familia/connection/handlers.rb +2 -0
- data/lib/familia/connection/individual_command_proxy.rb +2 -0
- data/lib/familia/connection/middleware.rb +34 -24
- data/lib/familia/connection/operation_core.rb +3 -2
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/pipelined_core.rb +3 -3
- data/lib/familia/connection/transaction_core.rb +69 -2
- data/lib/familia/connection.rb +18 -3
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +2 -0
- data/lib/familia/data_type/database_commands.rb +2 -0
- data/lib/familia/data_type/serialization.rb +79 -52
- data/lib/familia/data_type/settings.rb +2 -0
- data/lib/familia/data_type/types/counter.rb +2 -0
- data/lib/familia/data_type/types/hashkey.rb +7 -5
- data/lib/familia/data_type/types/listkey.rb +2 -0
- data/lib/familia/data_type/types/lock.rb +2 -0
- data/lib/familia/data_type/types/sorted_set.rb +7 -10
- data/lib/familia/data_type/types/stringkey.rb +24 -0
- data/lib/familia/data_type/types/unsorted_set.rb +2 -0
- data/lib/familia/data_type.rb +2 -0
- data/lib/familia/encryption/encrypted_data.rb +4 -2
- data/lib/familia/encryption/manager.rb +2 -0
- data/lib/familia/encryption/provider.rb +2 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
- data/lib/familia/encryption/registry.rb +2 -0
- data/lib/familia/encryption/request_cache.rb +2 -0
- data/lib/familia/encryption.rb +9 -2
- data/lib/familia/errors.rb +2 -0
- data/lib/familia/features/autoloader.rb +2 -0
- data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
- data/lib/familia/features/encrypted_fields.rb +2 -2
- data/lib/familia/features/expiration/extensions.rb +3 -1
- data/lib/familia/features/expiration.rb +12 -4
- data/lib/familia/features/external_identifier.rb +62 -7
- data/lib/familia/features/object_identifier.rb +49 -0
- data/lib/familia/features/quantization.rb +3 -1
- data/lib/familia/features/relationships/README.md +3 -1
- data/lib/familia/features/relationships/collection_operations.rb +2 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +97 -21
- data/lib/familia/features/relationships/indexing.rb +3 -0
- data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
- data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
- data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
- data/lib/familia/features/relationships/participation.rb +155 -69
- data/lib/familia/features/relationships/participation_membership.rb +69 -0
- data/lib/familia/features/relationships/participation_relationship.rb +34 -6
- data/lib/familia/features/relationships/score_encoding.rb +2 -0
- data/lib/familia/features/relationships.rb +5 -3
- data/lib/familia/features/safe_dump.rb +2 -0
- data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
- data/lib/familia/features/transient_fields.rb +2 -0
- data/lib/familia/features.rb +2 -0
- data/lib/familia/field_type.rb +3 -1
- data/lib/familia/horreum/connection.rb +17 -1
- data/lib/familia/horreum/database_commands.rb +8 -1
- data/lib/familia/horreum/definition.rb +16 -6
- data/lib/familia/horreum/management.rb +353 -52
- data/lib/familia/horreum/persistence.rb +179 -108
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +23 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -0
- data/lib/familia/horreum.rb +15 -1
- data/lib/familia/identifier_extractor.rb +3 -1
- data/lib/familia/instrumentation.rb +156 -0
- data/lib/familia/json_serializer.rb +2 -0
- data/lib/familia/logging.rb +92 -32
- data/lib/familia/refinements/dear_json.rb +2 -0
- data/lib/familia/refinements/stylize_words.rb +2 -14
- data/lib/familia/refinements/time_literals.rb +2 -0
- data/lib/familia/refinements.rb +2 -0
- data/lib/familia/secure_identifier.rb +10 -2
- data/lib/familia/settings.rb +2 -0
- data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
- data/lib/familia/thread_safety/monitor.rb +328 -0
- data/lib/familia/utils.rb +13 -0
- data/lib/familia/verifiable_identifier.rb +3 -1
- data/lib/familia/version.rb +3 -1
- data/lib/familia.rb +31 -4
- data/lib/middleware/database_command_counter.rb +152 -0
- data/lib/middleware/database_logger.rb +295 -170
- data/lib/multi_result.rb +61 -31
- data/try/edge_cases/empty_identifiers_try.rb +2 -0
- data/try/edge_cases/hash_symbolization_try.rb +2 -0
- data/try/edge_cases/json_serialization_try.rb +2 -0
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
- data/try/edge_cases/race_conditions_try.rb +4 -0
- data/try/edge_cases/reserved_keywords_try.rb +4 -0
- data/try/edge_cases/string_coercion_try.rb +2 -0
- data/try/edge_cases/ttl_side_effects_try.rb +4 -0
- data/try/features/count_any_edge_cases_try.rb +486 -0
- data/try/features/count_any_methods_try.rb +197 -0
- data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
- data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
- data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
- data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
- data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
- data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
- data/try/features/encrypted_fields/memory_security_try.rb +4 -0
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
- data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
- data/try/features/encryption/config_persistence_try.rb +4 -0
- data/try/features/encryption/core_try.rb +4 -0
- data/try/features/encryption/instance_variable_scope_try.rb +4 -0
- data/try/features/encryption/module_loading_try.rb +4 -0
- data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
- data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
- data/try/features/encryption/roundtrip_validation_try.rb +4 -0
- data/try/features/encryption/secure_memory_handling_try.rb +4 -0
- data/try/features/expiration/expiration_try.rb +4 -0
- data/try/features/external_identifier/external_identifier_try.rb +305 -8
- data/try/features/feature_dependencies_try.rb +2 -0
- data/try/features/feature_improvements_try.rb +2 -0
- data/try/features/field_groups_try.rb +2 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
- data/try/features/object_identifier/object_identifier_try.rb +140 -0
- data/try/features/quantization/quantization_try.rb +4 -0
- data/try/features/real_feature_integration_try.rb +2 -0
- data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
- data/try/features/relationships/indexing_rebuild_try.rb +606 -0
- data/try/features/relationships/indexing_try.rb +2 -0
- data/try/features/relationships/participation_bidirectional_try.rb +242 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
- data/try/features/relationships/participation_commands_verification_try.rb +2 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
- data/try/features/relationships/participation_reverse_index_try.rb +15 -13
- data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
- data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
- data/try/features/relationships/relationships_api_changes_try.rb +2 -0
- data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
- data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
- data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
- data/try/features/relationships/relationships_performance_try.rb +4 -0
- data/try/features/relationships/relationships_performance_working_try.rb +4 -0
- data/try/features/relationships/relationships_try.rb +6 -4
- data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
- data/try/features/safe_dump/safe_dump_try.rb +4 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/refresh_reset_try.rb +3 -0
- data/try/features/transient_fields/simple_refresh_test.rb +3 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
- data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
- data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
- data/try/integration/connection/handler_constraints_try.rb +4 -0
- data/try/integration/connection/isolated_dbclient_try.rb +4 -0
- data/try/integration/connection/middleware_reconnect_try.rb +2 -0
- data/try/integration/connection/operation_mode_guards_try.rb +4 -0
- data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
- data/try/integration/connection/pools_try.rb +4 -0
- data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
- data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
- data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
- data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
- data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
- data/try/integration/connection/transaction_modes_try.rb +4 -0
- data/try/integration/conventional_inheritance_try.rb +4 -0
- data/try/integration/create_method_try.rb +4 -0
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +9 -3
- data/try/integration/data_types/datatype_transactions_try.rb +17 -7
- data/try/integration/database_consistency_try.rb +4 -0
- data/try/integration/familia_extended_try.rb +4 -0
- data/try/integration/familia_members_methods_try.rb +4 -0
- data/try/integration/models/customer_safe_dump_try.rb +4 -0
- data/try/integration/models/customer_try.rb +7 -3
- data/try/integration/models/datatype_base_try.rb +4 -0
- data/try/integration/models/familia_object_try.rb +4 -0
- data/try/integration/persistence_operations_try.rb +4 -0
- data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
- data/try/integration/save_methods_consistency_try.rb +241 -0
- data/try/integration/scenarios_try.rb +4 -0
- data/try/integration/secure_identifier_try.rb +4 -0
- data/try/integration/transaction_safety_core_try.rb +176 -0
- data/try/integration/transaction_safety_workflow_try.rb +291 -0
- data/try/integration/verifiable_identifier_try.rb +4 -0
- data/try/investigation/pipeline_routing/README.md +228 -0
- data/try/performance/benchmarks_try.rb +4 -0
- data/try/performance/transaction_safety_benchmark_try.rb +238 -0
- data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
- data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
- data/try/support/debugging/cache_behavior_tracer.rb +4 -0
- data/try/support/debugging/debug_aad_process.rb +3 -0
- data/try/support/debugging/debug_concealed_internal.rb +3 -0
- data/try/support/debugging/debug_concealed_reveal.rb +3 -0
- data/try/support/debugging/debug_context_aad.rb +3 -0
- data/try/support/debugging/debug_context_simple.rb +3 -0
- data/try/support/debugging/debug_cross_context.rb +3 -0
- data/try/support/debugging/debug_database_load.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
- data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
- data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
- data/try/support/debugging/debug_field_decrypt.rb +3 -0
- data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
- data/try/support/debugging/debug_load_path.rb +3 -0
- data/try/support/debugging/debug_method_definition.rb +3 -0
- data/try/support/debugging/debug_method_resolution.rb +3 -0
- data/try/support/debugging/debug_minimal.rb +3 -0
- data/try/support/debugging/debug_provider.rb +3 -0
- data/try/support/debugging/debug_secure_behavior.rb +3 -0
- data/try/support/debugging/debug_string_class.rb +3 -0
- data/try/support/debugging/debug_test.rb +3 -0
- data/try/support/debugging/debug_test_design.rb +3 -0
- data/try/support/debugging/encryption_method_tracer.rb +4 -0
- data/try/support/debugging/provider_diagnostics.rb +4 -0
- data/try/support/helpers/test_cleanup.rb +4 -0
- data/try/support/helpers/test_helpers.rb +5 -0
- data/try/support/memory/memory_basic_test.rb +4 -0
- data/try/support/memory/memory_detailed_test.rb +4 -0
- data/try/support/memory/memory_search_for_string.rb +4 -0
- data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
- data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
- data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
- data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
- data/try/support/prototypes/atomic_saves_v4.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
- data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
- data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
- data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
- data/try/support/prototypes/pooling/pool_siege.rb +4 -2
- data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
- data/try/thread_safety/README.md +496 -0
- data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
- data/try/thread_safety/connection_chain_race_try.rb +148 -0
- data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
- data/try/thread_safety/feature_registry_race_try.rb +226 -0
- data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
- data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
- data/try/thread_safety/field_registration_race_try.rb +222 -0
- data/try/thread_safety/logger_initialization_race_try.rb +170 -0
- data/try/thread_safety/middleware_registration_race_try.rb +154 -0
- data/try/thread_safety/module_config_race_try.rb +175 -0
- data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
- data/try/unit/core/autoloader_try.rb +4 -0
- data/try/unit/core/base_enhancements_try.rb +4 -0
- data/try/unit/core/connection_try.rb +4 -0
- data/try/unit/core/errors_try.rb +4 -0
- data/try/unit/core/extensions_try.rb +4 -0
- data/try/unit/core/familia_logger_try.rb +2 -0
- data/try/unit/core/familia_try.rb +4 -0
- data/try/unit/core/middleware_sampling_try.rb +335 -0
- data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
- data/try/unit/core/middleware_thread_safety_try.rb +245 -0
- data/try/unit/core/middleware_try.rb +4 -0
- data/try/unit/core/settings_try.rb +4 -0
- data/try/unit/core/time_utils_try.rb +4 -0
- data/try/unit/core/tools_try.rb +4 -0
- data/try/unit/core/utils_try.rb +37 -0
- data/try/unit/data_types/boolean_try.rb +39 -22
- data/try/unit/data_types/counter_try.rb +4 -0
- data/try/unit/data_types/datatype_base_try.rb +4 -0
- data/try/unit/data_types/hash_try.rb +6 -2
- data/try/unit/data_types/list_try.rb +4 -0
- data/try/unit/data_types/lock_try.rb +4 -0
- data/try/unit/data_types/serialization_try.rb +386 -0
- data/try/unit/data_types/sorted_set_try.rb +4 -0
- data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
- data/try/unit/data_types/string_try.rb +4 -0
- data/try/unit/data_types/unsortedset_try.rb +4 -0
- data/try/unit/familia_resolve_class_try.rb +116 -0
- data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
- data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
- data/try/unit/horreum/base_try.rb +4 -0
- data/try/unit/horreum/class_methods_try.rb +4 -0
- data/try/unit/horreum/commands_try.rb +4 -0
- data/try/unit/horreum/defensive_initialization_try.rb +4 -0
- data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +6 -1
- data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
- data/try/unit/horreum/field_categories_try.rb +4 -0
- data/try/unit/horreum/field_definition_try.rb +4 -0
- data/try/unit/horreum/initialization_try.rb +4 -0
- data/try/unit/horreum/json_type_preservation_try.rb +2 -0
- data/try/unit/horreum/optimized_loading_try.rb +156 -0
- data/try/unit/horreum/relations_try.rb +4 -0
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +4 -0
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
- data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
- data/try/unit/middleware/database_logger_methods_try.rb +251 -0
- data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
- data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
- data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
- data/try/unit/thread_safety_monitor_try.rb +149 -0
- metadata +69 -17
- data/.github/workflows/code-quality.yml +0 -138
- data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
- data/changelog.d/20251011_203905_delano_next.rst +0 -30
- data/changelog.d/20251011_212633_delano_next.rst +0 -13
- data/changelog.d/20251011_221253_delano_next.rst +0 -26
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
- data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
- data/docs/archive/FAMILIA_UPDATE.md +0 -226
- data/docs/archive/README.md +0 -64
- data/docs/archive/api-reference.md +0 -333
- data/docs/guides/core-field-system.md +0 -806
- data/docs/guides/implementation.md +0 -276
- 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
|
-
# @
|
|
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
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
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
|
-
#
|
|
19
|
-
#
|
|
17
|
+
# This unifies behavior with Horreum fields (Issue #190), ensuring
|
|
18
|
+
# consistent type preservation across DataType and Horreum.
|
|
20
19
|
#
|
|
21
|
-
# @example
|
|
22
|
-
# serialize_value(
|
|
23
|
-
# serialize_value("hello") #=> "hello"
|
|
20
|
+
# @example Familia object reference
|
|
21
|
+
# serialize_value(customer_obj) #=> "customer_123" (identifier)
|
|
24
22
|
#
|
|
25
|
-
# @
|
|
26
|
-
#
|
|
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
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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}
|
|
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.
|
|
81
|
+
Familia.debug "deserialize_values: (#{@opts}) #{values}"
|
|
81
82
|
return [] if values.empty?
|
|
82
|
-
return values.flatten unless @opts[:class]
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
+
values.collect! do |obj|
|
|
91
|
+
next if obj.nil?
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
112
|
-
#
|
|
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
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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/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.
|
|
26
|
-
Familia.
|
|
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.
|
|
78
|
-
Familia.
|
|
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.
|
|
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/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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
data/lib/familia/data_type.rb
CHANGED
|
@@ -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.
|
|
20
|
+
Familia.debug "[valid?] result: #{result}, parsed: #{parsed}, required: #{required_fields}"
|
|
19
21
|
result
|
|
20
22
|
rescue Familia::SerializerError => e
|
|
21
|
-
Familia.
|
|
23
|
+
Familia.debug "[valid?] JSON error: #{e.message}"
|
|
22
24
|
false
|
|
23
25
|
end
|
|
24
26
|
end
|
data/lib/familia/encryption.rb
CHANGED
|
@@ -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
|
|
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
|
data/lib/familia/errors.rb
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
285
|
+
Familia.debug "[update_expiration] No expiration for #{self.class} (#{dbkey})"
|
|
284
286
|
return true
|
|
285
287
|
end
|
|
286
288
|
|
|
287
|
-
|
|
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,
|