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
|
@@ -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
|
-
|
|
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
|
-
|
|
318
|
-
actual_key.length
|
|
319
|
-
actual_key.start_with?("sk-")
|
|
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`
|
|
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
|
|
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
|
|
107
|
-
set :favorite_categories # Will
|
|
108
|
-
hashkey :preferences # Will
|
|
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
|
|
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:
|
|
120
|
-
# - customer:cust_123:
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
#
|
|
382
|
-
#
|
|
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
|
|
451
|
+
list :orders # ❌ Won't cascade - no default_expiration defined
|
|
405
452
|
end
|
|
406
453
|
|
|
407
|
-
# ✅ Fix:
|
|
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 "
|
|
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
|
|
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
|
-
##
|
|
582
|
+
## API Reference
|
|
510
583
|
|
|
511
|
-
###
|
|
584
|
+
### Instance Methods
|
|
512
585
|
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
518
|
-
|
|
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
|
-
|
|
592
|
+
**Returns:** Boolean indicating success
|
|
530
593
|
|
|
531
|
-
|
|
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
|
-
|
|
538
|
-
|
|
596
|
+
#### `ttl`
|
|
597
|
+
Get remaining TTL for this object.
|
|
539
598
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
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
|
-
|
|
607
|
+
**Returns:** Boolean - true if TTL is set
|
|
559
608
|
|
|
560
|
-
|
|
561
|
-
|
|
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
|
-
|
|
566
|
-
|
|
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
|
-
|
|
615
|
+
**Returns:** Boolean - true if expired/expiring
|
|
583
616
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
|
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.
|