familia 2.0.0.pre18 → 2.0.0.pre21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/CHANGELOG.rst +205 -88
- data/CLAUDE.md +62 -10
- data/Gemfile +3 -3
- data/Gemfile.lock +27 -62
- data/README.md +39 -0
- data/bin/try +16 -0
- data/bin/tryouts +16 -0
- data/changelog.d/20251105_flexible_external_identifier_format.rst +66 -0
- data/changelog.d/20251107_112554_delano_179_participation_asymmetry.rst +44 -0
- data/changelog.d/20251107_213121_delano_fix_thread_safety_races_011CUumCP492Twxm4NLt2FvL.rst +20 -0
- data/changelog.d/20251107_fix_participates_in_symbol_resolution.rst +91 -0
- data/changelog.d/20251107_optimized_redis_exists_checks.rst +94 -0
- data/changelog.d/20251108_frozen_string_literal_pragma.rst +44 -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 +177 -133
- 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-pre19.md +197 -0
- 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 +282 -0
- 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 +254 -0
- data/lib/familia/connection/handlers.rb +97 -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 -1
- data/lib/familia/connection/operations.rb +2 -0
- data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +4 -2
- data/lib/familia/connection/transaction_core.rb +75 -9
- data/lib/familia/connection.rb +21 -5
- data/lib/familia/data_type/class_methods.rb +3 -1
- data/lib/familia/data_type/connection.rb +153 -7
- data/lib/familia/data_type/database_commands.rb +9 -4
- data/lib/familia/data_type/serialization.rb +10 -4
- 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 +8 -6
- 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 +2 -0
- data/lib/familia/data_type/types/stringkey.rb +2 -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 +53 -14
- 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 +11 -11
- data/lib/familia/features/expiration.rb +29 -21
- data/lib/familia/features/external_identifier.rb +33 -7
- data/lib/familia/features/object_identifier.rb +2 -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 +177 -47
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +203 -63
- data/lib/familia/features/relationships/indexing.rb +40 -42
- data/lib/familia/features/relationships/indexing_relationship.rb +17 -5
- 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 +5 -2
- data/lib/familia/horreum/connection.rb +28 -36
- data/lib/familia/horreum/database_commands.rb +131 -10
- data/lib/familia/horreum/definition.rb +18 -7
- data/lib/familia/horreum/management.rb +233 -57
- data/lib/familia/horreum/persistence.rb +314 -122
- data/lib/familia/horreum/related_fields.rb +2 -0
- data/lib/familia/horreum/serialization.rb +26 -4
- data/lib/familia/horreum/settings.rb +2 -0
- data/lib/familia/horreum/utils.rb +2 -8
- data/lib/familia/horreum.rb +46 -13
- data/lib/familia/identifier_extractor.rb +2 -0
- data/lib/familia/instrumentation.rb +156 -0
- data/lib/familia/json_serializer.rb +2 -0
- data/lib/familia/logging.rb +94 -37
- 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 +9 -7
- 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 +325 -129
- data/lib/multi_result.rb +2 -0
- 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 +6 -4
- data/try/edge_cases/ttl_side_effects_try.rb +4 -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 +5 -1
- data/try/features/external_identifier/external_identifier_try.rb +171 -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 +2 -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 +600 -0
- data/try/features/relationships/indexing_try.rb +30 -4
- 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 +6 -4
- 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 +7 -3
- 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 +5 -1
- data/try/integration/connection/pipeline_fallback_integration_try.rb +15 -12
- 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 +26 -22
- data/try/integration/cross_component_try.rb +4 -0
- data/try/integration/data_types/datatype_pipelines_try.rb +108 -0
- data/try/integration/data_types/datatype_transactions_try.rb +251 -0
- 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 +9 -1
- data/try/integration/models/customer_try.rb +4 -0
- data/try/integration/models/datatype_base_try.rb +4 -0
- data/try/integration/models/familia_object_try.rb +5 -1
- data/try/integration/persistence_operations_try.rb +166 -10
- 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 +5 -1
- 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 +4 -0
- 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/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 +5 -1
- 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 +36 -16
- data/try/unit/horreum/automatic_index_validation_try.rb +255 -0
- data/try/unit/horreum/base_try.rb +5 -1
- data/try/unit/horreum/class_methods_try.rb +6 -2
- 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 +4 -0
- 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 +5 -1
- 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 +8 -4
- data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
- data/try/unit/horreum/serialization_try.rb +6 -2
- data/try/unit/horreum/settings_try.rb +4 -0
- data/try/unit/horreum/unique_index_edge_cases_try.rb +380 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +283 -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 +81 -14
- data/.github/workflows/code-quality.yml +0 -138
- 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
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
# Implementation Guide
|
|
2
|
+
|
|
3
|
+
## Architecture Overview
|
|
4
|
+
|
|
5
|
+
The encrypted fields feature uses a modular provider system with field transformation hooks:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
User Input → Field Setter → Provider Selection → Encryption → Valkey/Redis
|
|
9
|
+
Valkey/Redis → Algorithm Detection → Decryption → Field Getter → User Output
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### Provider Architecture
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
16
|
+
│ Manager │ │ Registry │ │ Providers │
|
|
17
|
+
│ │ │ │ │ │
|
|
18
|
+
│ - encrypt() │───→│ - get() │───→│ XChaCha20Poly │
|
|
19
|
+
│ - decrypt() │ │ - register() │ │ AES-GCM │
|
|
20
|
+
│ - derive_key() │ │ - available() │ │ │
|
|
21
|
+
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Core Components
|
|
25
|
+
|
|
26
|
+
### 1. Registry System
|
|
27
|
+
|
|
28
|
+
The Registry manages available encryption providers and selects the best one:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
module Familia::Encryption::Registry
|
|
32
|
+
# Auto-register available providers by priority
|
|
33
|
+
def self.setup!
|
|
34
|
+
|
|
35
|
+
# Get provider instance by algorithm
|
|
36
|
+
def self.get(algorithm)
|
|
37
|
+
|
|
38
|
+
# Get highest-priority available provider
|
|
39
|
+
def self.default_provider
|
|
40
|
+
|
|
41
|
+
# Get available algorithm names
|
|
42
|
+
def self.available_algorithms
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Manager Class
|
|
47
|
+
|
|
48
|
+
The Manager handles encryption/decryption operations with provider delegation:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
class Familia::Encryption::Manager
|
|
52
|
+
# Use specific algorithm or auto-select best
|
|
53
|
+
def initialize(algorithm: nil)
|
|
54
|
+
|
|
55
|
+
# Encrypt with context-specific key derivation
|
|
56
|
+
def encrypt(plaintext, context:, additional_data: nil)
|
|
57
|
+
|
|
58
|
+
# Decrypt with automatic algorithm detection
|
|
59
|
+
def decrypt(encrypted_json, context:, additional_data: nil)
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. Provider Interface
|
|
64
|
+
|
|
65
|
+
All providers implement a common interface:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
class Provider
|
|
69
|
+
ALGORITHM = 'algorithm-name'
|
|
70
|
+
NONCE_SIZE = 12 # or 24 for XChaCha20
|
|
71
|
+
AUTH_TAG_SIZE = 16
|
|
72
|
+
|
|
73
|
+
def self.available? # Check if dependencies are met
|
|
74
|
+
def self.priority # Higher = preferred (XChaCha20: 100, AES: 50)
|
|
75
|
+
|
|
76
|
+
def encrypt(plaintext, key, additional_data)
|
|
77
|
+
def decrypt(ciphertext, key, nonce, auth_tag, additional_data)
|
|
78
|
+
def derive_key(master_key, context, personal: nil)
|
|
79
|
+
def generate_nonce
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 4. Key Derivation
|
|
84
|
+
|
|
85
|
+
Each field gets a unique encryption key using provider-specific methods:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
Master Key + Field Context → Provider KDF → Field-Specific Key
|
|
89
|
+
|
|
90
|
+
XChaCha20-Poly1305: BLAKE2b with personalization
|
|
91
|
+
AES-256-GCM: HKDF-SHA256
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Implementation Steps
|
|
95
|
+
|
|
96
|
+
### Step 1: Enable Encryption
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
class MyModel < Familia::Horreum
|
|
100
|
+
# Add the feature
|
|
101
|
+
feature :encrypted_fields
|
|
102
|
+
|
|
103
|
+
# Define encrypted fields
|
|
104
|
+
encrypted_field :sensitive_data
|
|
105
|
+
encrypted_field :api_key
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Step 2: Configure Keys
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
# config/initializers/familia.rb
|
|
113
|
+
Familia.configure do |config|
|
|
114
|
+
config.encryption_keys = {
|
|
115
|
+
v1: ENV['FAMILIA_ENCRYPTION_KEY_V1']
|
|
116
|
+
}
|
|
117
|
+
config.current_key_version = :v1
|
|
118
|
+
config.encryption_personalization = 'MyApp-2024' # Optional
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Validate configuration at startup
|
|
122
|
+
Familia::Encryption.validate_configuration!
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Step 3: Generate Keys
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Generate a secure 256-bit key (32 bytes)
|
|
129
|
+
$ openssl rand -base64 32
|
|
130
|
+
# => base64_encoded_key_here
|
|
131
|
+
|
|
132
|
+
# Add to environment
|
|
133
|
+
$ echo "FAMILIA_ENCRYPTION_KEY_V1=base64_encoded_key_here" >> .env
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Step 4: Install Optional Dependencies
|
|
137
|
+
|
|
138
|
+
For best security and performance, install RbNaCl:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Add to Gemfile
|
|
142
|
+
gem 'rbnacl', '~> 7.1', '>= 7.1.1'
|
|
143
|
+
|
|
144
|
+
# Install
|
|
145
|
+
$ bundle install
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Without RbNaCl, Familia falls back to OpenSSL AES-256-GCM (still secure but lower priority).
|
|
149
|
+
|
|
150
|
+
## Advanced Usage
|
|
151
|
+
|
|
152
|
+
### Additional Authenticated Data (AAD)
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
class SecureDocument < Familia::Horreum
|
|
156
|
+
feature :encrypted_fields
|
|
157
|
+
|
|
158
|
+
field :doc_id, :owner_id, :classification
|
|
159
|
+
encrypted_field :content, aad_fields: [:doc_id, :owner_id, :classification]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# The content can only be decrypted if doc_id, owner_id, and classification
|
|
163
|
+
# values match those used during encryption
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Request-Level Caching
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
# For performance optimization
|
|
170
|
+
Familia::Encryption.with_request_cache do
|
|
171
|
+
vault.secret_key = "value1"
|
|
172
|
+
vault.api_token = "value2"
|
|
173
|
+
vault.save # Reuses derived keys within this block
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Cache is automatically cleared when block exits
|
|
177
|
+
# Or manually: Familia::Encryption.clear_request_cache!
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### ConcealedString Objects
|
|
181
|
+
|
|
182
|
+
Encrypted fields return ConcealedString objects to prevent accidental exposure:
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
secret = vault.secret_key
|
|
186
|
+
secret.class # => ConcealedString
|
|
187
|
+
puts secret # => "[CONCEALED]" (automatic redaction)
|
|
188
|
+
secret.inspect # => "[CONCEALED]" (automatic redaction)
|
|
189
|
+
|
|
190
|
+
# Safe access pattern - requires explicit reveal
|
|
191
|
+
secret.reveal do |raw_value|
|
|
192
|
+
# Use raw_value carefully - avoid creating copies
|
|
193
|
+
HTTP.post('/api', headers: { 'X-Token' => raw_value })
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Check if cleared from memory
|
|
197
|
+
secret.cleared? # Returns true if wiped
|
|
198
|
+
|
|
199
|
+
# Explicit cleanup
|
|
200
|
+
secret.clear! # Best-effort memory wiping
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Provider-Specific Features
|
|
204
|
+
|
|
205
|
+
### XChaCha20-Poly1305 Provider (Recommended)
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
# Enable with RbNaCl gem
|
|
209
|
+
gem 'rbnacl', '~> 7.1'
|
|
210
|
+
|
|
211
|
+
# Benefits:
|
|
212
|
+
# - Extended nonce (192 bits vs 96 bits)
|
|
213
|
+
# - Better resistance to nonce reuse
|
|
214
|
+
# - BLAKE2b key derivation with personalization
|
|
215
|
+
# - Priority: 100 (highest)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### AES-256-GCM Provider (Fallback)
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
# Always available with OpenSSL
|
|
222
|
+
# - 256-bit keys, 96-bit nonces (12 bytes)
|
|
223
|
+
# - HKDF-SHA256 key derivation
|
|
224
|
+
# - Priority: 50
|
|
225
|
+
# - Good compatibility, proven security
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Performance Optimization
|
|
229
|
+
|
|
230
|
+
### Provider Benchmarking
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
# Compare provider performance
|
|
234
|
+
results = Familia::Encryption.benchmark(iterations: 1000)
|
|
235
|
+
puts results
|
|
236
|
+
# => {
|
|
237
|
+
# "xchacha20poly1305" => { time: 0.45, ops_per_sec: 4444, priority: 100 },
|
|
238
|
+
# "aes-256-gcm" => { time: 0.52, ops_per_sec: 3846, priority: 50 }
|
|
239
|
+
# }
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Monitoring Key Derivation
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
# Monitor key derivations (should increment with each operation)
|
|
246
|
+
puts Familia::Encryption.derivation_count.value
|
|
247
|
+
# => 42
|
|
248
|
+
|
|
249
|
+
# Reset counter for testing
|
|
250
|
+
Familia::Encryption.reset_derivation_count!
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Encryption Status
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
# Get current encryption setup info
|
|
257
|
+
status = Familia::Encryption.status
|
|
258
|
+
# => {
|
|
259
|
+
# default_algorithm: "xchacha20poly1305",
|
|
260
|
+
# available_algorithms: ["xchacha20poly1305", "aes-256-gcm"],
|
|
261
|
+
# preferred_available: "Familia::Encryption::Providers::XChaCha20Poly1305Provider",
|
|
262
|
+
# using_hardware: false,
|
|
263
|
+
# key_versions: [:v1, :v2],
|
|
264
|
+
# current_version: :v2
|
|
265
|
+
# }
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Field-Level Features
|
|
269
|
+
|
|
270
|
+
### Instance Methods
|
|
271
|
+
|
|
272
|
+
```ruby
|
|
273
|
+
vault = Vault.new(secret_key: 'secret', api_token: 'token123')
|
|
274
|
+
|
|
275
|
+
# Check if any encrypted fields have values
|
|
276
|
+
vault.encrypted_data? # => true
|
|
277
|
+
|
|
278
|
+
# Clear all encrypted field values from memory
|
|
279
|
+
vault.clear_encrypted_fields!
|
|
280
|
+
|
|
281
|
+
# Check if all encrypted fields have been cleared
|
|
282
|
+
vault.encrypted_fields_cleared? # => true
|
|
283
|
+
|
|
284
|
+
# Re-encrypt all fields with current settings (for key rotation)
|
|
285
|
+
vault.re_encrypt_fields!
|
|
286
|
+
vault.save
|
|
287
|
+
|
|
288
|
+
# Get encryption status for all encrypted fields
|
|
289
|
+
status = vault.encrypted_fields_status
|
|
290
|
+
# => {
|
|
291
|
+
# secret_key: { encrypted: true, algorithm: "xchacha20poly1305", cleared: false },
|
|
292
|
+
# api_token: { encrypted: true, cleared: true }
|
|
293
|
+
# }
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Class Methods
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
class Vault < Familia::Horreum
|
|
300
|
+
feature :encrypted_fields
|
|
301
|
+
encrypted_field :secret_key
|
|
302
|
+
encrypted_field :api_token
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Get list of encrypted field names
|
|
306
|
+
Vault.encrypted_fields # => [:secret_key, :api_token]
|
|
307
|
+
|
|
308
|
+
# Check if a field is encrypted
|
|
309
|
+
Vault.encrypted_field?(:secret_key) # => true
|
|
310
|
+
Vault.encrypted_field?(:name) # => false
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Key Rotation
|
|
314
|
+
|
|
315
|
+
The feature supports key versioning for seamless key rotation:
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
# Step 1: Add new key version while keeping old keys
|
|
319
|
+
Familia.configure do |config|
|
|
320
|
+
config.encryption_keys = {
|
|
321
|
+
v1: old_key,
|
|
322
|
+
v2: new_key
|
|
323
|
+
}
|
|
324
|
+
config.current_key_version = :v2
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Step 2: Objects decrypt with any valid key, encrypt with current key
|
|
328
|
+
vault.secret_key = "new-secret" # Encrypted with v2 key
|
|
329
|
+
vault.save
|
|
330
|
+
|
|
331
|
+
# Step 3: Re-encrypt existing records
|
|
332
|
+
vault.re_encrypt_fields! # Uses current key version
|
|
333
|
+
vault.save
|
|
334
|
+
|
|
335
|
+
# Step 4: After all data is re-encrypted, remove old key
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Error Handling
|
|
339
|
+
|
|
340
|
+
The feature provides specific error types for different failure modes:
|
|
341
|
+
|
|
342
|
+
```ruby
|
|
343
|
+
# Invalid ciphertext or tampering
|
|
344
|
+
begin
|
|
345
|
+
vault.secret_key.reveal { |s| s }
|
|
346
|
+
rescue Familia::EncryptionError => e
|
|
347
|
+
# "Decryption failed - invalid key or corrupted data"
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Missing encryption configuration
|
|
351
|
+
Familia.config.encryption_keys = {}
|
|
352
|
+
begin
|
|
353
|
+
vault.secret_key.reveal { |s| s }
|
|
354
|
+
rescue Familia::EncryptionError => e
|
|
355
|
+
# "No encryption keys configured"
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Invalid key version
|
|
359
|
+
begin
|
|
360
|
+
vault.secret_key.reveal { |s| s }
|
|
361
|
+
rescue Familia::EncryptionError => e
|
|
362
|
+
# "No key for version: v1"
|
|
363
|
+
end
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Testing
|
|
367
|
+
|
|
368
|
+
```ruby
|
|
369
|
+
# Test helper setup
|
|
370
|
+
Familia.config.encryption_keys = { v1: Base64.strict_encode64('a' * 32) }
|
|
371
|
+
Familia.config.current_key_version = :v1
|
|
372
|
+
|
|
373
|
+
# In tests
|
|
374
|
+
it "encrypts sensitive fields" do
|
|
375
|
+
user = User.create(api_token: "secret-token")
|
|
376
|
+
|
|
377
|
+
# Verify encryption in Redis
|
|
378
|
+
raw_value = redis.hget(user.dbkey, "api_token")
|
|
379
|
+
expect(raw_value).not_to include("secret-token")
|
|
380
|
+
|
|
381
|
+
encrypted_data = JSON.parse(raw_value)
|
|
382
|
+
expect(encrypted_data).to have_key("ciphertext")
|
|
383
|
+
expect(encrypted_data).to have_key("algorithm")
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
it "provides concealed string access" do
|
|
387
|
+
user = User.create(api_token: "secret-token")
|
|
388
|
+
concealed = user.api_token
|
|
389
|
+
|
|
390
|
+
expect(concealed).to be_a(ConcealedString)
|
|
391
|
+
expect(concealed.to_s).to eq("[CONCEALED]")
|
|
392
|
+
|
|
393
|
+
concealed.reveal do |token|
|
|
394
|
+
expect(token).to eq("secret-token")
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## Security Model
|
|
400
|
+
|
|
401
|
+
### Ciphertext Format
|
|
402
|
+
|
|
403
|
+
Encrypted data is stored as JSON with algorithm-specific metadata:
|
|
404
|
+
|
|
405
|
+
```json
|
|
406
|
+
{
|
|
407
|
+
"algorithm": "xchacha20poly1305",
|
|
408
|
+
"nonce": "base64_encoded_nonce",
|
|
409
|
+
"ciphertext": "base64_encoded_data",
|
|
410
|
+
"auth_tag": "base64_encoded_tag",
|
|
411
|
+
"key_version": "v1"
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Memory Safety Limitations
|
|
416
|
+
|
|
417
|
+
⚠️ **Important**: Ruby provides NO memory safety guarantees:
|
|
418
|
+
- No secure memory wiping (best-effort only)
|
|
419
|
+
- Garbage collector may copy secrets
|
|
420
|
+
- String operations create uncontrolled copies
|
|
421
|
+
- Memory dumps may contain plaintext secrets
|
|
422
|
+
|
|
423
|
+
For highly sensitive applications, consider:
|
|
424
|
+
- External key management (HashiCorp Vault, AWS KMS)
|
|
425
|
+
- Hardware Security Modules (HSMs)
|
|
426
|
+
- Languages with secure memory handling
|
|
427
|
+
- Dedicated cryptographic appliances
|
|
428
|
+
|
|
429
|
+
### Threat Model
|
|
430
|
+
|
|
431
|
+
✅ **Protected Against:**
|
|
432
|
+
- Database compromise (encrypted data only)
|
|
433
|
+
- Field value swapping (field-specific keys)
|
|
434
|
+
- Cross-record attacks (record-specific keys)
|
|
435
|
+
- Tampering (authenticated encryption)
|
|
436
|
+
|
|
437
|
+
❌ **Not Protected Against:**
|
|
438
|
+
- Master key compromise (all data compromised)
|
|
439
|
+
- Application memory compromise (plaintext in RAM)
|
|
440
|
+
- Side-channel attacks (timing, power analysis)
|
|
441
|
+
- Insider threats with application access
|
|
442
|
+
|
|
443
|
+
## Troubleshooting
|
|
444
|
+
|
|
445
|
+
### Common Issues
|
|
446
|
+
|
|
447
|
+
1. **"No encryption key configured"**
|
|
448
|
+
- Ensure `FAMILIA_ENCRYPTION_KEY` is set
|
|
449
|
+
- Check `Familia.config.encryption_keys`
|
|
450
|
+
|
|
451
|
+
2. **"Decryption failed"**
|
|
452
|
+
- Verify correct key version
|
|
453
|
+
- Check if data was encrypted with different key
|
|
454
|
+
- Ensure AAD fields haven't changed
|
|
455
|
+
|
|
456
|
+
3. **Performance degradation**
|
|
457
|
+
- Enable request-level caching with `with_request_cache`
|
|
458
|
+
- Consider installing RbNaCl gem for XChaCha20
|
|
459
|
+
|
|
460
|
+
4. **Provider not available**
|
|
461
|
+
- Install RbNaCl for XChaCha20: `gem install rbnacl`
|
|
462
|
+
- Falls back to AES-256-GCM automatically
|
|
463
|
+
|
|
464
|
+
## API Reference
|
|
465
|
+
|
|
466
|
+
### Module Methods
|
|
467
|
+
|
|
468
|
+
```ruby
|
|
469
|
+
# Main encryption/decryption
|
|
470
|
+
Familia::Encryption.encrypt(plaintext, context:, additional_data: nil)
|
|
471
|
+
Familia::Encryption.decrypt(encrypted_json, context:, additional_data: nil)
|
|
472
|
+
Familia::Encryption.encrypt_with(algorithm, plaintext, context:, additional_data: nil)
|
|
473
|
+
|
|
474
|
+
# Configuration and status
|
|
475
|
+
Familia::Encryption.validate_configuration!
|
|
476
|
+
Familia::Encryption.status
|
|
477
|
+
Familia::Encryption.benchmark(iterations: 1000)
|
|
478
|
+
|
|
479
|
+
# Request caching
|
|
480
|
+
Familia::Encryption.with_request_cache { block }
|
|
481
|
+
Familia::Encryption.clear_request_cache!
|
|
482
|
+
|
|
483
|
+
# Monitoring
|
|
484
|
+
Familia::Encryption.derivation_count
|
|
485
|
+
Familia::Encryption.reset_derivation_count!
|
|
486
|
+
```
|
|
@@ -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
|