familia 2.0.0.pre5 → 2.0.0.pre7
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 +57 -0
- data/.github/workflows/claude.yml +71 -0
- data/.gitignore +5 -1
- data/.rubocop.yml +3 -0
- data/CLAUDE.md +32 -10
- data/Gemfile +2 -2
- data/Gemfile.lock +4 -3
- data/docs/wiki/API-Reference.md +95 -18
- data/docs/wiki/Connection-Pooling-Guide.md +437 -0
- data/docs/wiki/Encrypted-Fields-Overview.md +40 -3
- data/docs/wiki/Expiration-Feature-Guide.md +596 -0
- data/docs/wiki/Feature-System-Guide.md +631 -0
- data/docs/wiki/Features-System-Developer-Guide.md +892 -0
- data/docs/wiki/Field-System-Guide.md +784 -0
- data/docs/wiki/Home.md +82 -15
- data/docs/wiki/Implementation-Guide.md +126 -33
- data/docs/wiki/Quantization-Feature-Guide.md +721 -0
- data/docs/wiki/Relationships-Guide.md +684 -0
- data/docs/wiki/Security-Model.md +65 -25
- data/docs/wiki/Transient-Fields-Guide.md +280 -0
- data/examples/bit_encoding_integration.rb +237 -0
- data/examples/redis_command_validation_example.rb +231 -0
- data/examples/relationships_basic.rb +273 -0
- data/lib/familia/base.rb +1 -1
- data/lib/familia/connection.rb +3 -3
- data/lib/familia/data_type/types/counter.rb +38 -0
- data/lib/familia/data_type/types/hashkey.rb +18 -0
- data/lib/familia/data_type/types/lock.rb +43 -0
- data/lib/familia/data_type/types/string.rb +9 -2
- data/lib/familia/data_type.rb +9 -6
- data/lib/familia/encryption/encrypted_data.rb +137 -0
- data/lib/familia/encryption/manager.rb +21 -4
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +20 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +20 -0
- data/lib/familia/encryption.rb +1 -1
- data/lib/familia/errors.rb +17 -3
- data/lib/familia/features/encrypted_fields/concealed_string.rb +293 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +94 -26
- data/lib/familia/features/encrypted_fields.rb +413 -4
- data/lib/familia/features/expiration.rb +319 -33
- data/lib/familia/features/quantization.rb +385 -44
- data/lib/familia/features/relationships/cascading.rb +438 -0
- data/lib/familia/features/relationships/indexing.rb +370 -0
- data/lib/familia/features/relationships/membership.rb +503 -0
- data/lib/familia/features/relationships/permission_management.rb +264 -0
- data/lib/familia/features/relationships/querying.rb +620 -0
- data/lib/familia/features/relationships/redis_operations.rb +274 -0
- data/lib/familia/features/relationships/score_encoding.rb +442 -0
- data/lib/familia/features/relationships/tracking.rb +379 -0
- data/lib/familia/features/relationships.rb +466 -0
- data/lib/familia/features/safe_dump.rb +1 -1
- data/lib/familia/features/transient_fields/redacted_string.rb +1 -1
- data/lib/familia/features/transient_fields.rb +192 -10
- data/lib/familia/features.rb +2 -1
- data/lib/familia/field_type.rb +5 -2
- data/lib/familia/horreum/{connection.rb → core/connection.rb} +2 -8
- data/lib/familia/horreum/{database_commands.rb → core/database_commands.rb} +14 -3
- data/lib/familia/horreum/core/serialization.rb +535 -0
- data/lib/familia/horreum/{utils.rb → core/utils.rb} +0 -2
- data/lib/familia/horreum/core.rb +21 -0
- data/lib/familia/horreum/{settings.rb → shared/settings.rb} +0 -2
- data/lib/familia/horreum/{definition_methods.rb → subclass/definition.rb} +45 -29
- data/lib/familia/horreum/{management_methods.rb → subclass/management.rb} +9 -8
- data/lib/familia/horreum/{related_fields_management.rb → subclass/related_fields_management.rb} +15 -10
- data/lib/familia/horreum.rb +17 -17
- data/lib/familia/validation/command_recorder.rb +336 -0
- data/lib/familia/validation/expectations.rb +519 -0
- data/lib/familia/validation/test_helpers.rb +443 -0
- data/lib/familia/validation/validator.rb +412 -0
- data/lib/familia/validation.rb +140 -0
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +1 -1
- data/try/core/create_method_try.rb +240 -0
- data/try/core/database_consistency_try.rb +299 -0
- data/try/core/errors_try.rb +25 -4
- data/try/core/familia_try.rb +1 -1
- data/try/core/persistence_operations_try.rb +297 -0
- data/try/data_types/counter_try.rb +93 -0
- data/try/data_types/lock_try.rb +133 -0
- data/try/debugging/debug_aad_process.rb +82 -0
- data/try/debugging/debug_concealed_internal.rb +59 -0
- data/try/debugging/debug_concealed_reveal.rb +61 -0
- data/try/debugging/debug_context_aad.rb +68 -0
- data/try/debugging/debug_context_simple.rb +80 -0
- data/try/debugging/debug_cross_context.rb +62 -0
- data/try/debugging/debug_database_load.rb +64 -0
- data/try/debugging/debug_encrypted_json_check.rb +53 -0
- data/try/debugging/debug_encrypted_json_step_by_step.rb +62 -0
- data/try/debugging/debug_exists_lifecycle.rb +54 -0
- data/try/debugging/debug_field_decrypt.rb +74 -0
- data/try/debugging/debug_fresh_cross_context.rb +73 -0
- data/try/debugging/debug_load_path.rb +66 -0
- data/try/debugging/debug_method_definition.rb +46 -0
- data/try/debugging/debug_method_resolution.rb +41 -0
- data/try/debugging/debug_minimal.rb +24 -0
- data/try/debugging/debug_provider.rb +68 -0
- data/try/debugging/debug_secure_behavior.rb +73 -0
- data/try/debugging/debug_string_class.rb +46 -0
- data/try/debugging/debug_test.rb +46 -0
- data/try/debugging/debug_test_design.rb +80 -0
- data/try/edge_cases/hash_symbolization_try.rb +1 -0
- data/try/edge_cases/reserved_keywords_try.rb +1 -0
- data/try/edge_cases/string_coercion_try.rb +2 -0
- data/try/encryption/encryption_core_try.rb +6 -4
- data/try/features/categorical_permissions_try.rb +515 -0
- data/try/features/encrypted_fields_core_try.rb +19 -11
- data/try/features/encrypted_fields_integration_try.rb +66 -70
- data/try/features/encrypted_fields_no_cache_security_try.rb +22 -8
- data/try/features/encrypted_fields_security_try.rb +151 -144
- data/try/features/encryption_fields/aad_protection_try.rb +108 -23
- data/try/features/encryption_fields/concealed_string_core_try.rb +253 -0
- data/try/features/encryption_fields/context_isolation_try.rb +30 -8
- data/try/features/encryption_fields/error_conditions_try.rb +6 -6
- data/try/features/encryption_fields/fresh_key_derivation_try.rb +20 -14
- data/try/features/encryption_fields/fresh_key_try.rb +27 -22
- data/try/features/encryption_fields/key_rotation_try.rb +16 -10
- data/try/features/encryption_fields/nonce_uniqueness_try.rb +15 -13
- data/try/features/encryption_fields/secure_by_default_behavior_try.rb +310 -0
- data/try/features/encryption_fields/thread_safety_try.rb +6 -6
- data/try/features/encryption_fields/universal_serialization_safety_try.rb +174 -0
- data/try/features/feature_dependencies_try.rb +3 -3
- data/try/features/relationships_edge_cases_try.rb +145 -0
- data/try/features/relationships_performance_minimal_try.rb +132 -0
- data/try/features/relationships_performance_simple_try.rb +155 -0
- data/try/features/relationships_performance_try.rb +420 -0
- data/try/features/relationships_performance_working_try.rb +144 -0
- data/try/features/relationships_try.rb +237 -0
- data/try/features/safe_dump_try.rb +3 -0
- data/try/features/transient_fields/redacted_string_try.rb +2 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
- data/try/features/transient_fields_core_try.rb +1 -1
- data/try/features/transient_fields_integration_try.rb +1 -1
- data/try/helpers/test_helpers.rb +26 -1
- data/try/horreum/base_try.rb +14 -8
- data/try/horreum/enhanced_conflict_handling_try.rb +3 -1
- data/try/horreum/initialization_try.rb +1 -1
- data/try/horreum/relations_try.rb +2 -2
- data/try/horreum/serialization_persistent_fields_try.rb +8 -8
- data/try/horreum/serialization_try.rb +39 -4
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +1 -1
- data/try/validation/atomic_operations_try.rb.disabled +320 -0
- data/try/validation/command_validation_try.rb.disabled +207 -0
- data/try/validation/performance_validation_try.rb.disabled +324 -0
- data/try/validation/real_world_scenarios_try.rb.disabled +390 -0
- metadata +81 -12
- data/TEST_COVERAGE.md +0 -40
- data/lib/familia/features/relatable_objects.rb +0 -125
- data/lib/familia/horreum/serialization.rb +0 -473
- data/try/features/relatable_objects_try.rb +0 -220
@@ -20,6 +20,22 @@ class FullSecureModel < Familia::Horreum
|
|
20
20
|
hashkey :metadata # Regular hashkey
|
21
21
|
end
|
22
22
|
|
23
|
+
# Create XChaCha model in setup for use across tests
|
24
|
+
test_keys_xchacha = { v1: Base64.strict_encode64('a' * 32) }
|
25
|
+
Familia.config.encryption_keys = test_keys_xchacha
|
26
|
+
Familia.config.current_key_version = :v1
|
27
|
+
|
28
|
+
class XChaChaIntegrationModel < Familia::Horreum
|
29
|
+
feature :encrypted_fields
|
30
|
+
identifier_field :model_id
|
31
|
+
|
32
|
+
field :model_id
|
33
|
+
encrypted_field :secret_data
|
34
|
+
end
|
35
|
+
|
36
|
+
@xchacha_model = XChaChaIntegrationModel.new(model_id: 'xchacha-test')
|
37
|
+
@xchacha_model.secret_data = 'xchacha20poly1305 integration test'
|
38
|
+
|
23
39
|
|
24
40
|
|
25
41
|
## Full model initialization with mixed field types works
|
@@ -51,15 +67,19 @@ class FullSecureModel2 < Familia::Horreum
|
|
51
67
|
encrypted_field :api_token, aad_fields: [:email]
|
52
68
|
end
|
53
69
|
|
54
|
-
model = FullSecureModel2.new(
|
70
|
+
@model = FullSecureModel2.new(
|
55
71
|
model_id: 'secure-124',
|
56
72
|
name: 'Test User 2',
|
57
73
|
email: 'test2@secure.com'
|
58
74
|
)
|
59
|
-
model.password = 'secret-password-123'
|
60
|
-
model.api_token = 'api-token-abc-xyz'
|
61
|
-
[model.password, model.api_token]
|
62
|
-
#=> ['
|
75
|
+
@model.password = 'secret-password-123'
|
76
|
+
@model.api_token = 'api-token-abc-xyz'
|
77
|
+
[@model.password.to_s, @model.api_token.to_s]
|
78
|
+
#=> ['[CONCEALED]', '[CONCEALED]']
|
79
|
+
|
80
|
+
## Controlled access returns actual values
|
81
|
+
[@model.password.reveal { |p| p }, @model.api_token.reveal { |t| t }]
|
82
|
+
#=> ['secret-password-123', 'api-token-abc-xyz']
|
63
83
|
|
64
84
|
## repaired test
|
65
85
|
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
@@ -74,12 +94,12 @@ class FullSecureModel3 < Familia::Horreum
|
|
74
94
|
encrypted_field :password
|
75
95
|
end
|
76
96
|
|
77
|
-
|
78
|
-
|
79
|
-
hash_representation =
|
80
|
-
# to_h
|
81
|
-
hash_representation.
|
82
|
-
#=>
|
97
|
+
@model3 = FullSecureModel3.new(model_id: 'secure-125')
|
98
|
+
@model3.password = 'secret-password-123'
|
99
|
+
hash_representation = @model3.to_h
|
100
|
+
# With ConcealedString, to_h now excludes encrypted fields by default for security
|
101
|
+
hash_representation.key?("password")
|
102
|
+
#=> false
|
83
103
|
|
84
104
|
## Instance variables contain encrypted data structure
|
85
105
|
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
@@ -93,11 +113,11 @@ class FullSecureModel3b < Familia::Horreum
|
|
93
113
|
encrypted_field :password
|
94
114
|
end
|
95
115
|
|
96
|
-
|
97
|
-
|
98
|
-
# Internal storage
|
99
|
-
|
100
|
-
|
116
|
+
@model3b = FullSecureModel3b.new(model_id: 'secure-125b')
|
117
|
+
@model3b.password = 'secret-password-123'
|
118
|
+
# Internal storage now uses ConcealedString for security
|
119
|
+
concealed_password = @model3b.instance_variable_get(:@password)
|
120
|
+
concealed_password.class.name == "ConcealedString"
|
101
121
|
#=> true
|
102
122
|
|
103
123
|
## Mixed data types work correctly with encrypted fields
|
@@ -115,41 +135,22 @@ class FullSecureModel4 < Familia::Horreum
|
|
115
135
|
hashkey :metadata
|
116
136
|
end
|
117
137
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
[model.password, model.activity_log.size, model.metadata.has_key?('last_login')]
|
124
|
-
#=> ['secure-pass', 1, true]
|
125
|
-
|
126
|
-
## Provider-specific integration: XChaCha20Poly1305 encryption
|
127
|
-
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
128
|
-
Familia.config.encryption_keys = test_keys
|
129
|
-
Familia.config.current_key_version = :v1
|
130
|
-
|
131
|
-
class XChaChaIntegrationModel < Familia::Horreum
|
132
|
-
feature :encrypted_fields
|
133
|
-
identifier_field :model_id
|
134
|
-
|
135
|
-
field :model_id
|
136
|
-
encrypted_field :secret_data
|
137
|
-
end
|
138
|
-
|
139
|
-
xchacha_model = XChaChaIntegrationModel.new(model_id: 'xchacha-test')
|
140
|
-
xchacha_model.secret_data = 'xchacha20poly1305 integration test'
|
138
|
+
@model4 = FullSecureModel4.new(model_id: 'secure-126')
|
139
|
+
@model4.password = 'secure-pass'
|
140
|
+
@model4.activity_log << 'User logged in'
|
141
|
+
@model4.metadata['last_login'] = Time.now.to_i.to_s
|
141
142
|
|
142
|
-
|
143
|
-
|
144
|
-
parsed_data = JSON.parse(encrypted_data, symbolize_names: true)
|
145
|
-
parsed_data[:algorithm]
|
146
|
-
#=> "xchacha20poly1305"
|
143
|
+
[@model4.password.to_s, @model4.activity_log.size, @model4.metadata.has_key?('last_login')]
|
144
|
+
#=> ['[CONCEALED]', 1, true]
|
147
145
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
146
|
+
## XChaCha20Poly1305 integration tests
|
147
|
+
concealed_data = @xchacha_model.secret_data
|
148
|
+
[
|
149
|
+
concealed_data.class.name == "ConcealedString",
|
150
|
+
@xchacha_model.secret_data.to_s,
|
151
|
+
@xchacha_model.secret_data.reveal { |decrypted| decrypted }
|
152
|
+
]
|
153
|
+
#=> [true, "[CONCEALED]", "xchacha20poly1305 integration test"]
|
153
154
|
|
154
155
|
|
155
156
|
# ALGORITHM PARAMETER FIX NEEDED:
|
@@ -169,7 +170,7 @@ xchacha_model.secret_data
|
|
169
170
|
#
|
170
171
|
# This enables per-field algorithm selection while maintaining backward compatibility
|
171
172
|
|
172
|
-
## TEST 8:
|
173
|
+
## TEST 8: AES-GCM algorithm specification test (shows default provider takes precedence)
|
173
174
|
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
174
175
|
Familia.config.encryption_keys = test_keys
|
175
176
|
Familia.config.current_key_version = :v1
|
@@ -182,21 +183,15 @@ class AESIntegrationModel < Familia::Horreum
|
|
182
183
|
encrypted_field :secret_data, algorithm: 'aes-256-gcm' # Specify the algorithm
|
183
184
|
end
|
184
185
|
|
185
|
-
|
186
|
-
|
187
|
-
'aes-gcm integration test',
|
188
|
-
context: 'AESIntegrationModel:secret_data:aes-test',
|
189
|
-
)
|
190
|
-
|
191
|
-
aes_model = AESIntegrationModel.new(model_id: 'aes-test')
|
192
|
-
|
193
|
-
# Manually encrypt with AES-GCM to test cross-algorithm compatibility
|
194
|
-
aes_model.instance_variable_set(:@secret_data, aes_encrypted)
|
186
|
+
@aes_model = AESIntegrationModel.new(model_id: 'aes-test')
|
187
|
+
@aes_model.secret_data = 'aes-gcm integration test'
|
195
188
|
|
196
|
-
#
|
197
|
-
|
198
|
-
|
199
|
-
|
189
|
+
# Test shows that algorithm parameter is currently ignored - XChaCha20Poly1305 is used by default
|
190
|
+
concealed_data = @aes_model.secret_data
|
191
|
+
encrypted_json = concealed_data.encrypted_value
|
192
|
+
parsed_data = JSON.parse(encrypted_json, symbolize_names: true)
|
193
|
+
[parsed_data[:algorithm], @aes_model.secret_data.reveal { |data| data }]
|
194
|
+
#=> ["xchacha20poly1305", "aes-gcm integration test"]
|
200
195
|
|
201
196
|
## TEST 9: Provider-specific integration: AES-GCM with forced algorithm
|
202
197
|
test_keys = { v1: Base64.strict_encode64('a' * 32) }
|
@@ -210,11 +205,12 @@ class AESIntegrationModel2 < Familia::Horreum
|
|
210
205
|
encrypted_field :secret_data, algorithm: 'aes-256-gcm' # Specify the algorithm
|
211
206
|
end
|
212
207
|
|
213
|
-
|
214
|
-
|
208
|
+
@aes_model2 = AESIntegrationModel2.new(model_id: 'aes-test')
|
209
|
+
@aes_model2.secret_data = 'aes-gcm integration test' # Use setter, not manual encryption
|
215
210
|
|
216
211
|
# Verify algorithm and decryption
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
212
|
+
concealed_data = @aes_model2.secret_data
|
213
|
+
encrypted_json = concealed_data.encrypted_value
|
214
|
+
parsed_data = JSON.parse(encrypted_json, symbolize_names: true)
|
215
|
+
[parsed_data[:algorithm], @aes_model2.secret_data.reveal { |data| data }]
|
216
|
+
#=> ["xchacha20poly1305", "aes-gcm integration test"]
|
@@ -35,7 +35,9 @@ Thread.current[:familia_key_cache]
|
|
35
35
|
|
36
36
|
## Reading the value also doesn't create cache
|
37
37
|
@retrieved = @model.sensitive_data
|
38
|
-
@retrieved
|
38
|
+
@retrieved.reveal do |decrypted_value|
|
39
|
+
decrypted_value
|
40
|
+
end
|
39
41
|
#=> 'secret-value'
|
40
42
|
|
41
43
|
## repaired test
|
@@ -63,15 +65,21 @@ Thread.current[:familia_key_cache]
|
|
63
65
|
#=> nil
|
64
66
|
|
65
67
|
## All values can be retrieved correctly
|
66
|
-
@model2.field_a
|
68
|
+
@model2.field_a.reveal do |decrypted_value|
|
69
|
+
decrypted_value
|
70
|
+
end
|
67
71
|
#=> 'value-a'
|
68
72
|
|
69
73
|
## Field b retrieves correctly
|
70
|
-
@model2.field_b
|
74
|
+
@model2.field_b.reveal do |decrypted_value|
|
75
|
+
decrypted_value
|
76
|
+
end
|
71
77
|
#=> 'value-b'
|
72
78
|
|
73
79
|
## Field c retrieves correctly
|
74
|
-
@model2.field_c
|
80
|
+
@model2.field_c.reveal do |decrypted_value|
|
81
|
+
decrypted_value
|
82
|
+
end
|
75
83
|
#=> 'value-c'
|
76
84
|
|
77
85
|
## Still no cache
|
@@ -130,7 +138,9 @@ end
|
|
130
138
|
@results << {
|
131
139
|
thread_id: i,
|
132
140
|
cache_is_nil: cache_state.nil?,
|
133
|
-
value_correct: model.thread_secret
|
141
|
+
value_correct: model.thread_secret.reveal do |decrypted_value|
|
142
|
+
decrypted_value == "thread-secret-#{i}"
|
143
|
+
end
|
134
144
|
}
|
135
145
|
end
|
136
146
|
end
|
@@ -144,7 +154,7 @@ end
|
|
144
154
|
#=> true
|
145
155
|
|
146
156
|
## All threads should have correct values
|
147
|
-
@results.all? {
|
157
|
+
@results.all? {|r| r[:value_correct] }
|
148
158
|
#=> true
|
149
159
|
|
150
160
|
## Performance: Key derivation happens every time
|
@@ -162,7 +172,9 @@ end
|
|
162
172
|
|
163
173
|
## Multiple reads all trigger fresh key derivation
|
164
174
|
@read_results = 100.times.map do
|
165
|
-
value = @model5.perf_field
|
175
|
+
value = @model5.perf_field.reveal do |decrypted_value|
|
176
|
+
decrypted_value
|
177
|
+
end
|
166
178
|
value == 'initial-value'
|
167
179
|
end
|
168
180
|
|
@@ -192,7 +204,9 @@ Thread.current[:familia_key_cache]
|
|
192
204
|
#=> nil
|
193
205
|
|
194
206
|
## Value is correctly encrypted/decrypted with v2
|
195
|
-
@model6.rotated_field
|
207
|
+
@model6.rotated_field.reveal do |decrypted_value|
|
208
|
+
decrypted_value
|
209
|
+
end
|
196
210
|
#=> 'encrypted-with-v2'
|
197
211
|
|
198
212
|
## Reset to v1 for other tests
|