familia 2.0.0.pre3 → 2.0.0.pre5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop_todo.yml +17 -17
  4. data/CLAUDE.md +3 -3
  5. data/Gemfile +5 -1
  6. data/Gemfile.lock +18 -3
  7. data/README.md +36 -157
  8. data/TEST_COVERAGE.md +40 -0
  9. data/docs/overview.md +359 -0
  10. data/docs/wiki/API-Reference.md +270 -0
  11. data/docs/wiki/Encrypted-Fields-Overview.md +64 -0
  12. data/docs/wiki/Home.md +49 -0
  13. data/docs/wiki/Implementation-Guide.md +183 -0
  14. data/docs/wiki/Security-Model.md +143 -0
  15. data/lib/familia/base.rb +18 -27
  16. data/lib/familia/connection.rb +6 -5
  17. data/lib/familia/{datatype → data_type}/commands.rb +2 -5
  18. data/lib/familia/{datatype → data_type}/serialization.rb +8 -10
  19. data/lib/familia/{datatype → data_type}/types/hashkey.rb +2 -2
  20. data/lib/familia/{datatype → data_type}/types/list.rb +17 -18
  21. data/lib/familia/{datatype → data_type}/types/sorted_set.rb +17 -17
  22. data/lib/familia/{datatype → data_type}/types/string.rb +2 -1
  23. data/lib/familia/{datatype → data_type}/types/unsorted_set.rb +17 -18
  24. data/lib/familia/{datatype.rb → data_type.rb} +10 -12
  25. data/lib/familia/encryption/manager.rb +102 -0
  26. data/lib/familia/encryption/provider.rb +49 -0
  27. data/lib/familia/encryption/providers/aes_gcm_provider.rb +103 -0
  28. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +184 -0
  29. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +118 -0
  30. data/lib/familia/encryption/registry.rb +50 -0
  31. data/lib/familia/encryption.rb +178 -0
  32. data/lib/familia/encryption_request_cache.rb +68 -0
  33. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +153 -0
  34. data/lib/familia/features/encrypted_fields.rb +28 -0
  35. data/lib/familia/features/expiration.rb +107 -77
  36. data/lib/familia/features/quantization.rb +5 -9
  37. data/lib/familia/features/relatable_objects.rb +2 -4
  38. data/lib/familia/features/safe_dump.rb +14 -17
  39. data/lib/familia/features/transient_fields/redacted_string.rb +159 -0
  40. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +62 -0
  41. data/lib/familia/features/transient_fields/transient_field_type.rb +139 -0
  42. data/lib/familia/features/transient_fields.rb +47 -0
  43. data/lib/familia/features.rb +40 -24
  44. data/lib/familia/field_type.rb +270 -0
  45. data/lib/familia/horreum/connection.rb +8 -11
  46. data/lib/familia/horreum/{commands.rb → database_commands.rb} +7 -19
  47. data/lib/familia/horreum/definition_methods.rb +453 -0
  48. data/lib/familia/horreum/{class_methods.rb → management_methods.rb} +19 -229
  49. data/lib/familia/horreum/serialization.rb +46 -18
  50. data/lib/familia/horreum/settings.rb +10 -2
  51. data/lib/familia/horreum/utils.rb +9 -10
  52. data/lib/familia/horreum.rb +18 -10
  53. data/lib/familia/logging.rb +14 -14
  54. data/lib/familia/settings.rb +39 -3
  55. data/lib/familia/utils.rb +45 -0
  56. data/lib/familia/version.rb +1 -1
  57. data/lib/familia.rb +2 -1
  58. data/try/core/base_enhancements_try.rb +115 -0
  59. data/try/core/connection_try.rb +0 -1
  60. data/try/core/errors_try.rb +0 -1
  61. data/try/core/familia_extended_try.rb +3 -4
  62. data/try/core/familia_try.rb +0 -1
  63. data/try/core/pools_try.rb +2 -2
  64. data/try/core/secure_identifier_try.rb +0 -1
  65. data/try/core/settings_try.rb +0 -1
  66. data/try/core/utils_try.rb +0 -1
  67. data/try/{datatypes → data_types}/boolean_try.rb +1 -2
  68. data/try/{datatypes → data_types}/datatype_base_try.rb +2 -3
  69. data/try/{datatypes → data_types}/hash_try.rb +1 -2
  70. data/try/{datatypes → data_types}/list_try.rb +1 -2
  71. data/try/{datatypes → data_types}/set_try.rb +1 -2
  72. data/try/{datatypes → data_types}/sorted_set_try.rb +1 -2
  73. data/try/{datatypes → data_types}/string_try.rb +1 -2
  74. data/try/debugging/README.md +32 -0
  75. data/try/debugging/cache_behavior_tracer.rb +91 -0
  76. data/try/debugging/encryption_method_tracer.rb +138 -0
  77. data/try/debugging/provider_diagnostics.rb +110 -0
  78. data/try/edge_cases/hash_symbolization_try.rb +0 -1
  79. data/try/edge_cases/json_serialization_try.rb +0 -1
  80. data/try/edge_cases/reserved_keywords_try.rb +42 -11
  81. data/try/encryption/config_persistence_try.rb +192 -0
  82. data/try/encryption/encryption_core_try.rb +328 -0
  83. data/try/encryption/instance_variable_scope_try.rb +31 -0
  84. data/try/encryption/module_loading_try.rb +28 -0
  85. data/try/encryption/providers/aes_gcm_provider_try.rb +178 -0
  86. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +169 -0
  87. data/try/encryption/roundtrip_validation_try.rb +28 -0
  88. data/try/encryption/secure_memory_handling_try.rb +125 -0
  89. data/try/features/encrypted_fields_core_try.rb +117 -0
  90. data/try/features/encrypted_fields_integration_try.rb +220 -0
  91. data/try/features/encrypted_fields_no_cache_security_try.rb +205 -0
  92. data/try/features/encrypted_fields_security_try.rb +370 -0
  93. data/try/features/encryption_fields/aad_protection_try.rb +53 -0
  94. data/try/features/encryption_fields/context_isolation_try.rb +120 -0
  95. data/try/features/encryption_fields/error_conditions_try.rb +116 -0
  96. data/try/features/encryption_fields/fresh_key_derivation_try.rb +122 -0
  97. data/try/features/encryption_fields/fresh_key_try.rb +163 -0
  98. data/try/features/encryption_fields/key_rotation_try.rb +117 -0
  99. data/try/features/encryption_fields/memory_security_try.rb +37 -0
  100. data/try/features/encryption_fields/missing_current_key_version_try.rb +23 -0
  101. data/try/features/encryption_fields/nonce_uniqueness_try.rb +54 -0
  102. data/try/features/encryption_fields/thread_safety_try.rb +199 -0
  103. data/try/features/expiration_try.rb +0 -1
  104. data/try/features/feature_dependencies_try.rb +159 -0
  105. data/try/features/quantization_try.rb +0 -1
  106. data/try/features/real_feature_integration_try.rb +148 -0
  107. data/try/features/relatable_objects_try.rb +0 -1
  108. data/try/features/safe_dump_advanced_try.rb +0 -1
  109. data/try/features/safe_dump_try.rb +0 -1
  110. data/try/features/transient_fields/redacted_string_try.rb +248 -0
  111. data/try/features/transient_fields/refresh_reset_try.rb +164 -0
  112. data/try/features/transient_fields/simple_refresh_test.rb +50 -0
  113. data/try/features/transient_fields/single_use_redacted_string_try.rb +310 -0
  114. data/try/features/transient_fields_core_try.rb +181 -0
  115. data/try/features/transient_fields_integration_try.rb +260 -0
  116. data/try/helpers/test_helpers.rb +42 -0
  117. data/try/horreum/base_try.rb +157 -3
  118. data/try/horreum/class_methods_try.rb +27 -36
  119. data/try/horreum/enhanced_conflict_handling_try.rb +176 -0
  120. data/try/horreum/field_categories_try.rb +118 -0
  121. data/try/horreum/field_definition_try.rb +96 -0
  122. data/try/horreum/initialization_try.rb +0 -1
  123. data/try/horreum/relations_try.rb +0 -1
  124. data/try/horreum/serialization_persistent_fields_try.rb +165 -0
  125. data/try/horreum/serialization_try.rb +2 -3
  126. data/try/memory/memory_basic_test.rb +73 -0
  127. data/try/memory/memory_detailed_test.rb +121 -0
  128. data/try/memory/memory_docker_ruby_dump.sh +80 -0
  129. data/try/memory/memory_search_for_string.rb +83 -0
  130. data/try/memory/test_actual_redactedstring_protection.rb +38 -0
  131. data/try/models/customer_safe_dump_try.rb +0 -1
  132. data/try/models/customer_try.rb +0 -1
  133. data/try/models/datatype_base_try.rb +1 -2
  134. data/try/models/familia_object_try.rb +0 -1
  135. metadata +85 -18
@@ -1,16 +1,18 @@
1
1
  # lib/familia/settings.rb
2
2
 
3
3
  module Familia
4
-
5
4
  @delim = ':'
6
5
  @prefix = nil
7
6
  @suffix = :object
8
7
  @default_expiration = 0 # see update_expiration. Zero is skip. nil is an exception.
9
8
  @logical_database = nil
9
+ @encryption_keys = nil
10
+ @current_key_version = nil
11
+ @encryption_personalization = 'FamilialMatters'
10
12
 
11
13
  module Settings
12
-
13
- attr_writer :delim, :suffix, :default_expiration, :logical_database, :prefix
14
+ attr_writer :delim, :suffix, :default_expiration, :logical_database, :prefix, :encryption_keys,
15
+ :current_key_version, :encryption_personalization
14
16
 
15
17
  def delim(val = nil)
16
18
  @delim = val if val
@@ -44,5 +46,39 @@ module Familia
44
46
  suffix
45
47
  end
46
48
 
49
+ def encryption_keys(val = nil)
50
+ @encryption_keys = val if val
51
+ @encryption_keys
52
+ end
53
+
54
+ def current_key_version(val = nil)
55
+ @current_key_version = val if val
56
+ @current_key_version
57
+ end
58
+
59
+ # Personalization string for BLAKE2b key derivation in XChaCha20Poly1305.
60
+ # This provides cryptographic domain separation, ensuring derived keys are
61
+ # unique per application even with identical master keys and contexts.
62
+ # Must be 16 bytes or less (automatically padded with null bytes).
63
+ #
64
+ # @example
65
+ # Familia.configure do |config|
66
+ # config.encryption_personalization = 'MyApp1.0'
67
+ # end
68
+ #
69
+ # @param val [String, nil] The personalization string, or nil to get current value
70
+ # @return [String] Current personalization string
71
+ def encryption_personalization(val = nil)
72
+ if val
73
+ raise ArgumentError, 'Personalization string cannot exceed 16 bytes' if val.bytesize > 16
74
+
75
+ @encryption_personalization = val
76
+ end
77
+ @encryption_personalization
78
+ end
79
+
80
+ def config
81
+ self
82
+ end
47
83
  end
48
84
  end
data/lib/familia/utils.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # lib/familia/utils.rb
2
2
 
3
3
  module Familia
4
+
5
+ # Family-related utility methods
6
+ #
4
7
  module Utils
5
8
 
6
9
  # Joins array elements with Familia delimiter
@@ -145,5 +148,47 @@ module Familia
145
148
  end
146
149
  end
147
150
 
151
+ # Converts an absolute file path to a path relative to the current working
152
+ # directory. This simplifies logging and error reporting by showing
153
+ # only the relevant parts of file paths instead of lengthy absolute paths.
154
+ #
155
+ # @param filepath [String, Pathname] The file path to convert
156
+ # @return [Pathname, String, nil] A relative path from current directory,
157
+ # basename if path goes outside current directory, or nil if filepath is nil
158
+ #
159
+ # @example Using current directory as base
160
+ # Utils.pretty_path("/home/dev/project/lib/config.rb") # => "lib/config.rb"
161
+ #
162
+ # @example Path outside current directory
163
+ # Utils.pretty_path("/etc/hosts") # => "hosts"
164
+ #
165
+ # @example Nil input
166
+ # Utils.pretty_path(nil) # => nil
167
+ #
168
+ # @see Pathname#relative_path_from Ruby standard library documentation
169
+ def pretty_path(filepath)
170
+ return nil if filepath.nil?
171
+
172
+ basepath = Dir.pwd
173
+ relative_path = Pathname.new(filepath).relative_path_from(basepath)
174
+ if relative_path.to_s.start_with?('..')
175
+ File.basename(filepath)
176
+ else
177
+ relative_path
178
+ end
179
+ end
180
+
181
+ # Formats a stack trace with pretty file paths for improved readability
182
+ #
183
+ # @param limit [Integer] Maximum number of stack frames to include (default: 3)
184
+ # @return [String] Formatted stack trace with relative paths joined by newlines
185
+ #
186
+ # @example
187
+ # Utils.pretty_stack(limit: 10)
188
+ # # => "lib/models/user.rb:25:in `save'\n lib/controllers/app.rb:45:in `create'"
189
+ def pretty_stack(skip: 1, limit: 5)
190
+ caller(skip..(skip + limit + 1)).first(limit).map { |frame| pretty_path(frame) }.join("\n")
191
+ end
192
+
148
193
  end
149
194
  end
@@ -3,6 +3,6 @@
3
3
  module Familia
4
4
  # Version information for the Familia
5
5
  unless defined?(Familia::VERSION)
6
- VERSION = '2.0.0.pre3'
6
+ VERSION = '2.0.0.pre5'
7
7
  end
8
8
  end
data/lib/familia.rb CHANGED
@@ -81,5 +81,6 @@ end
81
81
 
82
82
  require_relative 'familia/base'
83
83
  require_relative 'familia/features'
84
- require_relative 'familia/datatype'
84
+ require_relative 'familia/data_type'
85
85
  require_relative 'familia/horreum'
86
+ require_relative 'familia/encryption'
@@ -0,0 +1,115 @@
1
+ # try/core/base_enhancements_try.rb
2
+
3
+ require_relative '../helpers/test_helpers'
4
+
5
+ Familia.debug = false
6
+
7
+ # Base class provides default UUID generation
8
+ class BaseUuidTest < Familia::Horreum
9
+ identifier_field :id
10
+ field :id
11
+ end
12
+
13
+ # Empty class still has base functionality
14
+ class EmptyBaseTest < Familia::Horreum
15
+ end
16
+
17
+ @base_uuid = BaseUuidTest.new(id: 'uuid_test_1')
18
+
19
+ ## UUID generation creates unique identifiers
20
+ @uuid1 = @base_uuid.uuid
21
+ @uuid1
22
+ #=:> String
23
+
24
+ ## UUID is properly formatted
25
+ @uuid1
26
+ #=~>/\A[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i
27
+
28
+ ## UUID is memoized (same value on repeated calls)
29
+ @uuid2 = @base_uuid.uuid
30
+ @uuid1 == @uuid2
31
+ #=> true
32
+
33
+ ## Different instances get different UUIDs
34
+ @base_uuid2 = BaseUuidTest.new(id: 'uuid_test_2')
35
+ @base_uuid.uuid != @base_uuid2.uuid
36
+ #=> true
37
+
38
+ ## Base class provides ID generation
39
+ @generated_id1 = @base_uuid.generate_id
40
+ @generated_id1
41
+ #=:> String
42
+
43
+ ## Generated ID is memoized
44
+ @generated_id2 = @base_uuid.generate_id
45
+ @generated_id1 == @generated_id2
46
+ #=> true
47
+
48
+ ## Base class to_s method returns identifier
49
+ @base_uuid.to_s
50
+ #=> "uuid_test_1"
51
+
52
+ ## Feature registry is accessible
53
+ Familia::Base.features_available
54
+ #=:> Hash
55
+
56
+ ## Feature definitions registry is accessible
57
+ Familia::Base.feature_definitions
58
+ #=:> Hash
59
+
60
+ ## Base class includes proper modules
61
+ BaseUuidTest.ancestors.include?(Familia::Base)
62
+ #=> true
63
+
64
+ ## Feature methods are accessible through the class
65
+ BaseUuidTest.respond_to?(:feature)
66
+ #=> true
67
+
68
+ ## Class instance variables are properly initialized
69
+ BaseUuidTest.instance_variable_get(:@fields)
70
+ #=:> Array
71
+
72
+ ## Field definitions are properly initialized
73
+ BaseUuidTest.instance_variable_get(:@field_types)
74
+ #=:> Hash
75
+
76
+ ## Feature system is properly integrated
77
+ BaseUuidTest.respond_to?(:feature)
78
+ #=> true
79
+
80
+ ## Feature registration methods are available
81
+ Familia::Base.respond_to?(:add_feature)
82
+ #=> true
83
+
84
+ ## Valid identifiers work correctly
85
+ @base_uuid.identifier
86
+ #=> "uuid_test_1"
87
+
88
+ ## Empty class has base methods available
89
+ EmptyBaseTest.ancestors.include?(Familia::Base)
90
+ #=> true
91
+
92
+ ## Empty class can use feature system
93
+ EmptyBaseTest.respond_to?(:feature)
94
+ #=> true
95
+
96
+ ## Test Base module constants are defined
97
+ Familia::Base.features_available
98
+ #=:> Hash
99
+
100
+ ## Dump and load methods are set
101
+ Familia::Base.dump_method
102
+ #=> :to_json
103
+
104
+ ## Load method is set correctly
105
+ Familia::Base.load_method
106
+ #=> :from_json
107
+
108
+ ## Base module provides inspect with class name
109
+ @base_uuid.inspect.include?('BaseUuidTest')
110
+ #=> true
111
+
112
+ @base_uuid.destroy! rescue nil
113
+ @base_uuid2.destroy! rescue nil
114
+ @base_uuid = nil
115
+ @base_uuid2 = nil
@@ -1,6 +1,5 @@
1
1
  # try/core/connection_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  Familia.debug = false
@@ -1,6 +1,5 @@
1
1
  # try/core/errors_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  Familia.debug = false
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'time'
4
4
 
5
- require_relative '../../lib/familia'
6
5
  require_relative '../helpers/test_helpers'
7
6
 
8
7
  ## Has all datatype relativess
@@ -11,15 +10,15 @@ registered_types.collect(&:to_s).sort
11
10
  #=> ["counter", "hash", "hashkey", "list", "lock", "set", "sorted_set", "string", "zset"]
12
11
 
13
12
  ## Familia created class methods for datatype list class
14
- Familia::Horreum::ClassMethods.public_method_defined? :list?
13
+ Familia::Horreum::DefinitionMethods.public_method_defined? :list?
15
14
  #=> true
16
15
 
17
16
  ## Familia created class methods for datatype list class
18
- Familia::Horreum::ClassMethods.public_method_defined? :list
17
+ Familia::Horreum::DefinitionMethods.public_method_defined? :list
19
18
  #=> true
20
19
 
21
20
  ## Familia created class methods for datatype list class
22
- Familia::Horreum::ClassMethods.public_method_defined? :lists
21
+ Familia::Horreum::DefinitionMethods.public_method_defined? :lists
23
22
  #=> true
24
23
 
25
24
  ## A Familia object knows its datatype relatives
@@ -1,6 +1,5 @@
1
1
  # try/core/familia_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  ## Check for help class
@@ -30,7 +30,7 @@ end
30
30
  class PoolTestAccount < Familia::Horreum
31
31
  identifier_field :account_id
32
32
  field :account_id
33
- field :balance
33
+ field :balance, on_conflict: :skip
34
34
  field :holder_name
35
35
 
36
36
  def init
@@ -83,7 +83,7 @@ class PoolTestAccountDB1 < Familia::Horreum
83
83
  self.logical_database = 1
84
84
  identifier_field :account_id
85
85
  field :account_id
86
- field :balance
86
+ field :balance, on_conflict: :skip
87
87
  field :holder_name
88
88
 
89
89
  def init
@@ -2,7 +2,6 @@
2
2
 
3
3
  # Test Familia::SecureIdentifier methods
4
4
 
5
- require_relative '../../lib/familia'
6
5
  require_relative '../helpers/test_helpers'
7
6
 
8
7
  Familia.debug = false
@@ -1,6 +1,5 @@
1
1
  # try/core/settings_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  Familia.debug = false
@@ -1,6 +1,5 @@
1
1
  # try/core/utils_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  Familia.debug = false
@@ -1,6 +1,5 @@
1
- # try/datatypes/boolean_try.rb
1
+ # try/data_types/boolean_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  Familia.debug = false
@@ -1,6 +1,5 @@
1
- # try/datatypes/base_try.rb
1
+ # try/data_types/base_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  @limiter1 = Limiter.new :requests
@@ -64,6 +63,6 @@ p [@limiter1.counter.parent.default_expiration, @limiter2.counter.parent.default
64
63
  #=> 3600.0
65
64
 
66
65
  ## Check current_expiration
67
- sleep 1 # Database default_expirations are in seconds so we can't wait any less time than this (without mocking)
66
+ sleep 1 # NOTE: Mocking time would be foolish in life, but helpful here
68
67
  @limiter1.counter.current_expiration
69
68
  #=> 3600-1
@@ -1,6 +1,5 @@
1
- # try/datatypes/hash_try.rb
1
+ # try/data_types/hash_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  @a = Bone.new 'atoken'
@@ -1,6 +1,5 @@
1
- # try/datatypes/list_try.rb
1
+ # try/data_types/list_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  @a = Bone.new 'atoken'
@@ -1,6 +1,5 @@
1
- # try/datatypes/set_try.rb
1
+ # try/data_types/set_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  @a = Bone.new 'atoken'
@@ -1,6 +1,5 @@
1
- # try/datatypes/sorted_set_try.rb
1
+ # try/data_types/sorted_set_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  @a = Bone.new 'atoken'
@@ -1,6 +1,5 @@
1
- # try/datatypes/string_try.rb
1
+ # try/data_types/string_try.rb
2
2
 
3
- require_relative '../../lib/familia'
4
3
  require_relative '../helpers/test_helpers'
5
4
 
6
5
  @a = Bone.new(token: 'atoken2')
@@ -0,0 +1,32 @@
1
+ # Familia Encryption Debugging Tools
2
+
3
+ Debug scripts for encryption issues. All scripts run safely without modifying data.
4
+
5
+ ## Quick Reference
6
+
7
+ | Issue | Script | Purpose |
8
+ |-------|--------|---------|
9
+ | Provider problems | `provider_diagnostics.rb` | Test provider registration, availability, compatibility |
10
+ | Performance issues | `cache_behavior_tracer.rb` | Analyze key derivation cache efficiency |
11
+ | Field operations | `encryption_method_tracer.rb` | Trace encrypt/decrypt calls with timing |
12
+
13
+ ```bash
14
+ # Run any script
15
+ ruby try/debugging/[script_name].rb
16
+ ```
17
+
18
+ ## Usage Guide
19
+
20
+ **Provider Issues**: Algorithm not found, registration failures
21
+ → `provider_diagnostics.rb`
22
+
23
+ **Slow Performance**: Excessive key derivations, cache misses
24
+ → `cache_behavior_tracer.rb` then `encryption_method_tracer.rb`
25
+
26
+ **Field Bugs**: AAD problems, context issues, decryption failures
27
+ → `encryption_method_tracer.rb`
28
+
29
+ ## Output Key
30
+ - **SUCCESS/FAILED/ERROR** = test results
31
+ - `version:context => [bytes]` = cache entries
32
+ - Timing in milliseconds = performance data
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+ # Debug script to trace encryption cache behavior and key derivation
3
+
4
+ require 'base64'
5
+ require 'bundler/setup'
6
+ require_relative '../helpers/test_helpers'
7
+
8
+ puts "=== Encryption Cache Behavior Tracer ==="
9
+ puts
10
+
11
+ # Setup test configuration
12
+ test_keys = { v1: Base64.strict_encode64('a' * 32) }
13
+ Familia.config.encryption_keys = test_keys
14
+ Familia.config.current_key_version = :v1
15
+
16
+ # Clear any existing cache
17
+ Thread.current[:familia_key_cache] = nil
18
+ puts "1. Initial Cache State:"
19
+ puts " Cache: #{Thread.current[:familia_key_cache].inspect}"
20
+ puts
21
+
22
+ # Define test model with multiple encrypted fields
23
+ class CacheTraceModel < Familia::Horreum
24
+ feature :encrypted_fields
25
+ identifier_field :user_id
26
+
27
+ field :user_id
28
+ encrypted_field :field_a
29
+ encrypted_field :field_b
30
+ encrypted_field :field_c
31
+ end
32
+
33
+ puts "2. Model Creation:"
34
+ user = CacheTraceModel.new(user_id: 'cache-user-1')
35
+ puts " After model creation: #{Thread.current[:familia_key_cache].inspect}"
36
+ puts
37
+
38
+ puts "3. Setting Encrypted Fields:"
39
+ ['field_a', 'field_b', 'field_c'].each_with_index do |field, i|
40
+ puts " Setting #{field}..."
41
+ user.public_send("#{field}=", "test-value-#{i+1}")
42
+ cache = Thread.current[:familia_key_cache]
43
+ if cache
44
+ puts " Cache size: #{cache.size}"
45
+ puts " Cache keys: #{cache.keys.inspect}"
46
+ else
47
+ puts " Cache: nil"
48
+ end
49
+ end
50
+ puts
51
+
52
+ puts "4. Reading Encrypted Fields:"
53
+ ['field_a', 'field_b', 'field_c'].each do |field|
54
+ puts " Reading #{field}..."
55
+ value = user.public_send(field)
56
+ puts " Retrieved: #{value}"
57
+ cache = Thread.current[:familia_key_cache]
58
+ if cache
59
+ puts " Cache size: #{cache.size}"
60
+ puts " Cache keys: #{cache.keys.inspect}"
61
+ else
62
+ puts " Cache: nil"
63
+ end
64
+ end
65
+ puts
66
+
67
+ puts "5. Final Cache Analysis:"
68
+ cache = Thread.current[:familia_key_cache]
69
+ if cache && !cache.empty?
70
+ puts " Cache size: #{cache.size} entries"
71
+ cache.each_with_index do |(key, value), i|
72
+ puts " Entry #{i+1}: #{key} => [#{value.bytesize} bytes]"
73
+ end
74
+ else
75
+ puts " Cache is empty or nil"
76
+ end
77
+ puts
78
+
79
+ # Test cache behavior with multiple models
80
+ puts "6. Multiple Models Cache Test:"
81
+ user2 = CacheTraceModel.new(user_id: 'cache-user-2')
82
+ user2.field_a = 'different-value'
83
+ puts " After second model field set:"
84
+ cache = Thread.current[:familia_key_cache]
85
+ if cache
86
+ puts " Cache size: #{cache.size}"
87
+ puts " Unique contexts: #{cache.keys.map { |k| k.split(':').last }.uniq.size}"
88
+ end
89
+
90
+ puts
91
+ puts "=== Cache Tracing Complete ==="
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env ruby
2
+ # Debug script to trace encryption method calls and data flow
3
+
4
+ require 'base64'
5
+ require 'bundler/setup'
6
+ require_relative '../helpers/test_helpers'
7
+
8
+ puts "=== Encryption Method Call Tracer ==="
9
+ puts
10
+
11
+ # Monkey patch to add detailed tracing
12
+ module Familia
13
+ module Encryption
14
+ class << self
15
+ # Store original methods
16
+ alias_method :orig_encrypt, :encrypt
17
+ alias_method :orig_decrypt, :decrypt
18
+
19
+ def encrypt(plaintext, context:, additional_data: nil)
20
+ puts "📤 ENCRYPT called:"
21
+ puts " Context: #{context}"
22
+ puts " Plaintext length: #{plaintext&.length} chars"
23
+ puts " AAD: #{additional_data ? 'present' : 'none'}"
24
+
25
+ start_time = Time.now
26
+ result = orig_encrypt(plaintext, context: context, additional_data: additional_data)
27
+ elapsed = ((Time.now - start_time) * 1000).round(2)
28
+
29
+ puts " Result length: #{result ? result.length : 'nil'} chars"
30
+ puts " Elapsed: #{elapsed}ms"
31
+ puts
32
+ result
33
+ end
34
+
35
+ def decrypt(encrypted_json, context:, additional_data: nil)
36
+ puts "📥 DECRYPT called:"
37
+ puts " Context: #{context}"
38
+ puts " Encrypted length: #{encrypted_json&.length} chars"
39
+ puts " AAD: #{additional_data ? 'present' : 'none'}"
40
+
41
+ start_time = Time.now
42
+ begin
43
+ result = orig_decrypt(encrypted_json, context: context, additional_data: additional_data)
44
+ elapsed = ((Time.now - start_time) * 1000).round(2)
45
+
46
+ puts " Result length: #{result ? result.length : 'nil'} chars"
47
+ puts " Elapsed: #{elapsed}ms"
48
+ puts
49
+ result
50
+ rescue => e
51
+ elapsed = ((Time.now - start_time) * 1000).round(2)
52
+ puts " ERROR: #{e.class}: #{e.message}"
53
+ puts " Elapsed: #{elapsed}ms"
54
+ puts
55
+ raise
56
+ end
57
+ end
58
+ end
59
+
60
+ class Manager
61
+ alias_method :orig_derive_key_with_provider, :derive_key_with_provider
62
+
63
+ def derive_key_with_provider(provider, context, version: nil)
64
+ puts "🔑 DERIVE_KEY called:"
65
+ puts " Provider: #{provider.class.name}"
66
+ puts " Context: #{context}"
67
+ puts " Version: #{version || 'current'}"
68
+
69
+ cache = Thread.current[:familia_key_cache] ||= {}
70
+ cache_key = "#{version || current_key_version}:#{context}"
71
+ puts " Cache key: #{cache_key}"
72
+ puts " Cache before: #{cache.keys.inspect}"
73
+
74
+ start_time = Time.now
75
+ result = orig_derive_key_with_provider(provider, context, version: version)
76
+ elapsed = ((Time.now - start_time) * 1000).round(2)
77
+
78
+ cache_after = Thread.current[:familia_key_cache] || {}
79
+ puts " Cache after: #{cache_after.keys.inspect}"
80
+ puts " Derived key: [#{result.bytesize} bytes]"
81
+ puts " Elapsed: #{elapsed}ms"
82
+ puts
83
+ result
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ # Setup test configuration
90
+ test_keys = { v1: Base64.strict_encode64('a' * 32) }
91
+ Familia.config.encryption_keys = test_keys
92
+ Familia.config.current_key_version = :v1
93
+
94
+ # Clear cache
95
+ Thread.current[:familia_key_cache] = nil
96
+
97
+ # Define test model
98
+ class TraceTestModel < Familia::Horreum
99
+ feature :encrypted_fields
100
+ identifier_field :user_id
101
+
102
+ field :user_id
103
+ encrypted_field :password
104
+ encrypted_field :api_key, aad_fields: [:user_id]
105
+ end
106
+
107
+ puts "=== Test Scenario: Field Operations ==="
108
+ puts
109
+
110
+ puts "Creating model and setting encrypted fields..."
111
+ user = TraceTestModel.new(user_id: 'trace-user-1')
112
+
113
+ puts "Setting password (no AAD)..."
114
+ user.password = 'secret-password-123'
115
+
116
+ puts "Setting api_key (with AAD)..."
117
+ user.api_key = 'api-key-xyz-789'
118
+
119
+ puts "Reading password..."
120
+ retrieved_password = user.password
121
+ puts "Final password value: #{retrieved_password}"
122
+
123
+ puts "Reading api_key..."
124
+ retrieved_api_key = user.api_key
125
+ puts "Final api_key value: #{retrieved_api_key}"
126
+
127
+ puts
128
+ puts "=== Test Scenario: Cross-Algorithm Decryption ==="
129
+ puts
130
+
131
+ # Test cross-algorithm compatibility
132
+ aes_encrypted = Familia::Encryption.encrypt_with('aes-256-gcm', 'cross-test-data', context: 'cross-test')
133
+ puts "Decrypting AES-GCM data with default manager..."
134
+ cross_decrypted = Familia::Encryption.decrypt(aes_encrypted, context: 'cross-test')
135
+ puts "Cross-algorithm result: #{cross_decrypted}"
136
+
137
+ puts
138
+ puts "=== Method Tracing Complete ==="