familia 2.0.0.pre7 → 2.0.0.pre10

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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +13 -0
  3. data/.github/workflows/docs.yml +1 -1
  4. data/.gitignore +9 -9
  5. data/.rubocop.yml +19 -0
  6. data/.yardopts +22 -1
  7. data/CHANGELOG.md +184 -0
  8. data/CLAUDE.md +8 -5
  9. data/Gemfile +1 -1
  10. data/Gemfile.lock +3 -3
  11. data/README.md +97 -2
  12. data/changelog.d/README.md +66 -0
  13. data/changelog.d/fragments/.keep +0 -0
  14. data/changelog.d/template.md.j2 +29 -0
  15. data/docs/archive/.gitignore +2 -0
  16. data/docs/archive/FAMILIA_RELATIONSHIPS.md +210 -0
  17. data/docs/archive/FAMILIA_TECHNICAL.md +823 -0
  18. data/docs/archive/FAMILIA_UPDATE.md +226 -0
  19. data/docs/archive/README.md +67 -0
  20. data/docs/guides/.gitignore +2 -0
  21. data/docs/{wiki → guides}/Feature-System-Guide.md +0 -15
  22. data/docs/{wiki → guides}/Relationships-Guide.md +103 -50
  23. data/docs/guides/relationships-methods.md +266 -0
  24. data/examples/relationships_basic.rb +90 -157
  25. data/familia.gemspec +4 -4
  26. data/lib/familia/connection.rb +4 -21
  27. data/lib/familia/features/external_identifiers/external_identifier_field_type.rb +120 -0
  28. data/lib/familia/features/external_identifiers.rb +111 -0
  29. data/lib/familia/features/object_identifiers/object_identifier_field_type.rb +91 -0
  30. data/lib/familia/features/object_identifiers.rb +194 -0
  31. data/lib/familia/features/relationships/cascading.rb +0 -1
  32. data/lib/familia/features/relationships/indexing.rb +160 -176
  33. data/lib/familia/features/relationships/membership.rb +16 -22
  34. data/lib/familia/features/relationships/querying.rb +7 -12
  35. data/lib/familia/features/relationships/score_encoding.rb +1 -3
  36. data/lib/familia/features/relationships/tracking.rb +61 -22
  37. data/lib/familia/features/relationships.rb +15 -8
  38. data/lib/familia/features/transient_fields.rb +8 -10
  39. data/lib/familia/features.rb +16 -13
  40. data/lib/familia/horreum/core/serialization.rb +2 -5
  41. data/lib/familia/horreum/subclass/definition.rb +36 -0
  42. data/lib/familia/horreum.rb +15 -24
  43. data/lib/familia/version.rb +1 -3
  44. data/setup.cfg +12 -0
  45. data/try/core/errors_try.rb +1 -1
  46. data/try/features/{encrypted_fields_core_try.rb → encrypted_fields/encrypted_fields_core_try.rb} +1 -1
  47. data/try/features/{encrypted_fields_integration_try.rb → encrypted_fields/encrypted_fields_integration_try.rb} +1 -1
  48. data/try/features/{encrypted_fields_no_cache_security_try.rb → encrypted_fields/encrypted_fields_no_cache_security_try.rb} +1 -1
  49. data/try/features/{encrypted_fields_security_try.rb → encrypted_fields/encrypted_fields_security_try.rb} +1 -1
  50. data/try/features/{expiration_try.rb → expiration/expiration_try.rb} +1 -1
  51. data/try/features/external_identifiers/external_identifiers_try.rb +203 -0
  52. data/try/features/object_identifiers/object_identifiers_integration_try.rb +289 -0
  53. data/try/features/object_identifiers/object_identifiers_try.rb +191 -0
  54. data/try/features/{quantization_try.rb → quantization/quantization_try.rb} +1 -1
  55. data/try/features/{categorical_permissions_try.rb → relationships/categorical_permissions_try.rb} +1 -1
  56. data/try/features/relationships/relationships_api_changes_try.rb +339 -0
  57. data/try/features/{relationships_edge_cases_try.rb → relationships/relationships_edge_cases_try.rb} +1 -1
  58. data/try/features/{relationships_performance_minimal_try.rb → relationships/relationships_performance_minimal_try.rb} +1 -1
  59. data/try/features/{relationships_performance_simple_try.rb → relationships/relationships_performance_simple_try.rb} +1 -1
  60. data/try/features/{relationships_performance_try.rb → relationships/relationships_performance_try.rb} +1 -1
  61. data/try/features/{relationships_performance_working_try.rb → relationships/relationships_performance_working_try.rb} +1 -1
  62. data/try/features/{relationships_try.rb → relationships/relationships_try.rb} +7 -6
  63. data/try/features/{safe_dump_advanced_try.rb → safe_dump/safe_dump_advanced_try.rb} +1 -1
  64. data/try/features/{safe_dump_try.rb → safe_dump/safe_dump_try.rb} +1 -1
  65. data/try/features/{transient_fields_core_try.rb → transient_fields/transient_fields_core_try.rb} +1 -1
  66. data/try/features/{transient_fields_integration_try.rb → transient_fields/transient_fields_integration_try.rb} +1 -1
  67. metadata +80 -60
  68. /data/docs/{wiki → guides}/API-Reference.md +0 -0
  69. /data/docs/{wiki → guides}/Connection-Pooling-Guide.md +0 -0
  70. /data/docs/{wiki → guides}/Encrypted-Fields-Overview.md +0 -0
  71. /data/docs/{wiki → guides}/Expiration-Feature-Guide.md +0 -0
  72. /data/docs/{wiki → guides}/Features-System-Developer-Guide.md +0 -0
  73. /data/docs/{wiki → guides}/Field-System-Guide.md +0 -0
  74. /data/docs/{wiki → guides}/Home.md +0 -0
  75. /data/docs/{wiki → guides}/Implementation-Guide.md +0 -0
  76. /data/docs/{wiki → guides}/Quantization-Feature-Guide.md +0 -0
  77. /data/docs/{wiki → guides}/Security-Model.md +0 -0
  78. /data/docs/{wiki → guides}/Transient-Fields-Guide.md +0 -0
  79. /data/try/features/{encryption_fields → encrypted_fields}/aad_protection_try.rb +0 -0
  80. /data/try/features/{encryption_fields → encrypted_fields}/concealed_string_core_try.rb +0 -0
  81. /data/try/features/{encryption_fields → encrypted_fields}/context_isolation_try.rb +0 -0
  82. /data/try/features/{encryption_fields → encrypted_fields}/error_conditions_try.rb +0 -0
  83. /data/try/features/{encryption_fields → encrypted_fields}/fresh_key_derivation_try.rb +0 -0
  84. /data/try/features/{encryption_fields → encrypted_fields}/fresh_key_try.rb +0 -0
  85. /data/try/features/{encryption_fields → encrypted_fields}/key_rotation_try.rb +0 -0
  86. /data/try/features/{encryption_fields → encrypted_fields}/memory_security_try.rb +0 -0
  87. /data/try/features/{encryption_fields → encrypted_fields}/missing_current_key_version_try.rb +0 -0
  88. /data/try/features/{encryption_fields → encrypted_fields}/nonce_uniqueness_try.rb +0 -0
  89. /data/try/features/{encryption_fields → encrypted_fields}/secure_by_default_behavior_try.rb +0 -0
  90. /data/try/features/{encryption_fields → encrypted_fields}/thread_safety_try.rb +0 -0
  91. /data/try/features/{encryption_fields → encrypted_fields}/universal_serialization_safety_try.rb +0 -0
@@ -34,6 +34,35 @@ module Familia
34
34
  word.to_s.split('_').map(&:capitalize).join
35
35
  end
36
36
 
37
+ # Define a class-level tracked collection
38
+ #
39
+ # @param collection_name [Symbol] Name of the class-level collection
40
+ # @param score [Symbol, Proc, nil] How to calculate the score
41
+ # @param on_destroy [Symbol] What to do when object is destroyed (:remove, :ignore)
42
+ #
43
+ # @example Class-level tracking (using class_ prefix convention)
44
+ # class_tracked_in :all_customers, score: :created_at
45
+ # class_tracked_in :active_users, score: -> { status == 'active' ? Time.now.to_i : 0 }
46
+ def class_tracked_in(collection_name, score: nil, on_destroy: :remove)
47
+
48
+ klass_name = (name || self.to_s).downcase
49
+
50
+ # Store metadata for this tracking relationship
51
+ tracking_relationships << {
52
+ context_class: klass_name,
53
+ context_class_name: name || self.to_s,
54
+ collection_name: collection_name,
55
+ score: score,
56
+ on_destroy: on_destroy
57
+ }
58
+
59
+ # Generate class-level collection methods
60
+ generate_tracking_class_methods(self, collection_name)
61
+
62
+ # Generate instance methods for class-level tracking
63
+ generate_tracking_instance_methods('class', collection_name, score)
64
+ end
65
+
37
66
  # Define a tracked_in relationship
38
67
  #
39
68
  # @param context_class [Class, Symbol] The class that owns the collection
@@ -49,10 +78,9 @@ module Familia
49
78
  # tracked_in Team, :domains, score: :added_at
50
79
  # tracked_in Organization, :all_domains, score: :created_at
51
80
  def tracked_in(context_class, collection_name, score: nil, on_destroy: :remove)
52
- # Handle special :global context
53
- if context_class == :global
54
- context_class_name = 'Global'
55
- elsif context_class.is_a?(Class)
81
+
82
+ # Handle class context
83
+ if context_class.is_a?(Class)
56
84
  class_name = context_class.name
57
85
  context_class_name = if class_name.include?('::')
58
86
  # Extract the last part after the last ::
@@ -60,7 +88,6 @@ module Familia
60
88
  else
61
89
  class_name
62
90
  end
63
- # Extract just the class name, handling anonymous classes
64
91
  else
65
92
  context_class_name = camelize_word(context_class)
66
93
  end
@@ -74,12 +101,8 @@ module Familia
74
101
  on_destroy: on_destroy
75
102
  }
76
103
 
77
- # Generate class methods on the context class (skip for global)
78
- if context_class == :global
79
- generate_global_class_methods(self, collection_name)
80
- else
81
- generate_context_class_methods(context_class, collection_name)
82
- end
104
+ # Generate context class methods
105
+ generate_context_class_methods(context_class, collection_name)
83
106
 
84
107
  # Generate instance methods on this class
85
108
  generate_tracking_instance_methods(context_class_name, collection_name, score)
@@ -92,21 +115,21 @@ module Familia
92
115
 
93
116
  private
94
117
 
95
- # Generate global collection methods (e.g., Domain.global_all_domains)
96
- def generate_global_class_methods(target_class, collection_name)
97
- # Generate global collection getter method
98
- target_class.define_singleton_method("global_#{collection_name}") do
99
- collection_key = "global:#{collection_name}"
118
+ # Generate class-level collection methods (e.g., User.all_users)
119
+ def generate_tracking_class_methods(target_class, collection_name)
120
+ # Generate class-level collection getter method
121
+ target_class.define_singleton_method("#{collection_name}") do
122
+ collection_key = "#{self.name.downcase}:#{collection_name}"
100
123
  Familia::SortedSet.new(nil, dbkey: collection_key, logical_database: logical_database)
101
124
  end
102
125
 
103
- # Generate global add method (e.g., Domain.add_to_global_all_domains)
126
+ # Generate class-level add method (e.g., User.add_to_all_users)
104
127
  target_class.define_singleton_method("add_to_#{collection_name}") do |item, score = nil|
105
- collection = send("global_#{collection_name}")
128
+ collection = send("#{collection_name}")
106
129
 
107
130
  # Calculate score if not provided
108
131
  score ||= if item.respond_to?(:calculate_tracking_score)
109
- item.calculate_tracking_score(:global, collection_name)
132
+ item.calculate_tracking_score('class', collection_name)
110
133
  else
111
134
  item.current_score
112
135
  end
@@ -117,9 +140,9 @@ module Familia
117
140
  collection.add(score, item.identifier)
118
141
  end
119
142
 
120
- # Generate global remove method
143
+ # Generate class-level remove method
121
144
  target_class.define_singleton_method("remove_from_#{collection_name}") do |item|
122
- collection = send("global_#{collection_name}")
145
+ collection = send("#{collection_name}")
123
146
  collection.delete(item.identifier)
124
147
  end
125
148
  end
@@ -304,6 +327,23 @@ module Familia
304
327
  end
305
328
  end
306
329
 
330
+ # Add to class-level tracking collections automatically
331
+ def add_to_class_tracking_collections
332
+ return unless self.class.respond_to?(:tracking_relationships)
333
+
334
+ self.class.tracking_relationships.each do |config|
335
+ context_class_name = config[:context_class_name]
336
+ context_class = config[:context_class]
337
+ collection_name = config[:collection_name]
338
+
339
+ # Only auto-add to class-level collections (where context_class matches self.class)
340
+ if context_class_name.downcase == self.class.name.downcase
341
+ # Call the class method to add this object
342
+ self.class.send("add_to_#{collection_name}", self)
343
+ end
344
+ end
345
+ end
346
+
307
347
  # Remove from all tracking collections (used during destroy)
308
348
  def remove_from_all_tracking_collections
309
349
  return unless self.class.respond_to?(:tracking_relationships)
@@ -372,7 +412,6 @@ module Familia
372
412
  memberships
373
413
  end
374
414
  end
375
-
376
415
  end
377
416
  end
378
417
  end
@@ -49,8 +49,8 @@ module Familia
49
49
  # tracked_in Organization, :all_domains, score: :created_at
50
50
  #
51
51
  # # O(1) lookups with Redis hashes
52
- # indexed_by :display_name, in: Customer, index_name: :domain_index
53
- # indexed_by :display_name, in: :global, index_name: :global_domain_index
52
+ # indexed_by :display_name, :domain_index, context: Customer
53
+ # indexed_by :display_name, :global_domain_index, context: :global
54
54
  #
55
55
  # # Context-aware membership (no method collisions)
56
56
  # member_of Customer, :domains
@@ -66,7 +66,7 @@ module Familia
66
66
  #
67
67
  # # Indexing methods
68
68
  # Customer.find_by_display_name(name) # O(1) lookup
69
- # Domain.find_by_display_name_globally(name) # Global lookup
69
+ # Domain.find_by_display_name(name) # Global lookup
70
70
  #
71
71
  # # Membership methods (collision-free naming)
72
72
  # domain.add_to_customer_domains(customer) # Specific collection
@@ -264,15 +264,22 @@ module Familia
264
264
  # This can be overridden by subclasses to set up initial relationships
265
265
  end
266
266
 
267
- # Override save to update relationships
267
+ # Override save to update relationships automatically
268
268
  def save(update_expiration: true)
269
269
  result = super
270
270
 
271
- if result && respond_to?(:update_all_indexes)
272
- # Update all indexes with current field values
273
- update_all_indexes
271
+ if result
272
+ # Automatically update all indexes when object is saved
273
+ if respond_to?(:update_all_indexes)
274
+ update_all_indexes
275
+ end
276
+
277
+ # Auto-add to class-level tracking collections
278
+ if respond_to?(:add_to_class_tracking_collections)
279
+ add_to_class_tracking_collections
280
+ end
274
281
 
275
- # NOTE: Tracking and membership updates are typically done explicitly
282
+ # NOTE: Relationship-specific membership and tracking updates are done explicitly
276
283
  # since we need to know which specific collections this object should be in
277
284
  end
278
285
 
@@ -182,9 +182,7 @@ module Familia
182
182
  def clear_transient_fields!
183
183
  self.class.transient_fields.each do |field_name|
184
184
  field_value = instance_variable_get("@#{field_name}")
185
- if field_value.respond_to?(:clear!)
186
- field_value.clear!
187
- end
185
+ field_value.clear! if field_value.respond_to?(:clear!)
188
186
  end
189
187
  end
190
188
 
@@ -213,13 +211,13 @@ module Familia
213
211
  def transient_fields_summary
214
212
  self.class.transient_fields.each_with_object({}) do |field_name, summary|
215
213
  field_value = instance_variable_get("@#{field_name}")
216
- if field_value.nil?
217
- summary[field_name] = nil
218
- elsif field_value.respond_to?(:cleared?) && field_value.cleared?
219
- summary[field_name] = "[CLEARED]"
220
- else
221
- summary[field_name] = "[REDACTED]"
222
- end
214
+ summary[field_name] = if field_value.nil?
215
+ nil
216
+ elsif field_value.respond_to?(:cleared?) && field_value.cleared?
217
+ '[CLEARED]'
218
+ else
219
+ '[REDACTED]'
220
+ end
223
221
  end
224
222
  end
225
223
 
@@ -1,17 +1,15 @@
1
1
  # lib/familia/features.rb
2
2
 
3
3
  module Familia
4
-
5
4
  FeatureDefinition = Data.define(:name, :depends_on)
6
5
 
7
6
  # Familia::Features
8
7
  #
9
8
  module Features
10
-
11
9
  @features_enabled = nil
12
10
  attr_reader :features_enabled
13
11
 
14
- def feature(feature_name = nil)
12
+ def feature(feature_name = nil, **options)
15
13
  @features_enabled ||= []
16
14
 
17
15
  return features_enabled if feature_name.nil?
@@ -28,22 +26,28 @@ module Familia
28
26
  return
29
27
  end
30
28
 
31
- if Familia.debug?
32
- Familia.trace :FEATURE, nil, "#{self} includes #{feature_name.inspect}", caller(1..1)
29
+ Familia.trace :FEATURE, nil, "#{self} includes #{feature_name.inspect}", caller(1..1) if Familia.debug?
30
+
31
+ # Check dependencies and raise error if missing
32
+ feature_def = Familia::Base.feature_definitions[feature_name]
33
+ if feature_def&.depends_on&.any?
34
+ missing = feature_def.depends_on - features_enabled
35
+ if missing.any?
36
+ raise Familia::Problem,
37
+ "Feature #{feature_name} requires missing dependencies: #{missing.join(', ')}"
38
+ end
33
39
  end
34
40
 
35
41
  # Add it to the list available features_enabled for Familia::Base classes.
36
42
  features_enabled << feature_name
37
43
 
38
- klass = Familia::Base.features_available[feature_name]
39
-
40
- # Validate dependencies
41
- feature_def = Familia::Base.feature_definitions[feature_name]
42
- if feature_def&.depends_on&.any?
43
- missing = feature_def.depends_on - features_enabled
44
- raise Familia::Problem, "#{feature_name} requires: #{missing.join(', ')}" if missing.any?
44
+ # Store feature options if any were provided using the new pattern
45
+ if options.any?
46
+ add_feature_options(feature_name, **options)
45
47
  end
46
48
 
49
+ klass = Familia::Base.features_available[feature_name]
50
+
47
51
  # Extend the Familia::Base subclass (e.g. Customer) with the feature module
48
52
  include klass
49
53
 
@@ -58,7 +62,6 @@ module Familia
58
62
  # We'd need to extend the DataType instances for each Horreum subclass. That
59
63
  # avoids it getting included multiple times per DataType
60
64
  end
61
-
62
65
  end
63
66
  end
64
67
 
@@ -135,7 +135,7 @@ module Familia
135
135
  multi.hmset(dbkey, to_h_for_storage)
136
136
  end
137
137
 
138
- result.is_a?(Array) # transaction succeeded
138
+ result.is_a?(Array) # transaction succeeded
139
139
  end
140
140
  end
141
141
 
@@ -463,9 +463,7 @@ module Familia
463
463
  #
464
464
  def serialize_value(val)
465
465
  # Security: Handle ConcealedString safely - extract encrypted data for storage
466
- if val.respond_to?(:encrypted_value)
467
- return val.encrypted_value
468
- end
466
+ return val.encrypted_value if val.respond_to?(:encrypted_value)
469
467
 
470
468
  prepared = Familia.distinguisher(val, strict_values: false)
471
469
 
@@ -530,6 +528,5 @@ module Familia
530
528
  end
531
529
  end
532
530
  end
533
-
534
531
  end
535
532
  end
@@ -177,6 +177,8 @@ module Familia
177
177
  # configuration values. This is particularly useful when mapping
178
178
  # familia models with specific database numbers in the configuration.
179
179
  #
180
+ # Familia::Horreum::DefinitionMethods#config_name
181
+ #
180
182
  # @example V2::Session.config_name => 'session'
181
183
  #
182
184
  # @return [String] The underscored class name as a string
@@ -235,6 +237,40 @@ module Familia
235
237
  field_types[field_type.name] = field_type
236
238
  end
237
239
 
240
+ # Get feature options for a specific feature or all features
241
+ #
242
+ # @param feature_name [Symbol, nil] The feature name to get options for
243
+ # @return [Hash] The options hash for the feature, or empty hash if none
244
+ #
245
+ def feature_options(feature_name = nil)
246
+ @feature_options ||= {}
247
+ return @feature_options if feature_name.nil?
248
+
249
+ @feature_options[feature_name.to_sym] || {}
250
+ end
251
+
252
+ # Add feature options for a specific feature
253
+ #
254
+ # This method provides a clean way for features to set their default options
255
+ # without worrying about initialization state. Similar to register_field_type
256
+ # for field types.
257
+ #
258
+ # @param feature_name [Symbol] The feature name
259
+ # @param options [Hash] The options to add/merge
260
+ # @return [Hash] The updated options for the feature
261
+ #
262
+ def add_feature_options(feature_name, **options)
263
+ @feature_options ||= {}
264
+ @feature_options[feature_name.to_sym] ||= {}
265
+
266
+ # Only set defaults for options that don't already exist
267
+ options.each do |key, value|
268
+ @feature_options[feature_name.to_sym][key] ||= value
269
+ end
270
+
271
+ @feature_options[feature_name.to_sym]
272
+ end
273
+
238
274
  # Create and register a transient field type
239
275
  #
240
276
  # @param name [Symbol] The field name
@@ -104,39 +104,30 @@ module Familia
104
104
  args = []
105
105
  end
106
106
 
107
- # Initialize object with arguments using one of three strategies:
107
+ # Initialize object with arguments using one of four strategies:
108
108
  #
109
- # 1. **Keyword Arguments** (Recommended): Order-independent field assignment
110
- # Example: Customer.new(name: "John", email: "john@example.com")
111
- # - Robust against field reordering
112
- # - Self-documenting
113
- # - Only sets provided fields
109
+ # 1. **Identifier** (Recommended for lookups): A single argument is treated as the identifier.
110
+ # Example: Customer.new("cust_123")
111
+ # - Robust and convenient for creating objects from an ID.
114
112
  #
115
- # 2. **Positional Arguments** (Legacy): Field assignment by definition order
116
- # Example: Customer.new("john@example.com", "password123")
117
- # - Brittle: breaks if field order changes
118
- # - Compact syntax
119
- # - Maps to fields in class definition order
113
+ # 2. **Keyword Arguments** (Recommended for creation): Order-independent field assignment
114
+ # Example: Customer.new(name: "John", email: "john@example.com")
120
115
  #
121
- # 3. **No Arguments**: Object created with all fields as nil
122
- # - Minimal memory footprint in Redis
123
- # - Fields set on-demand via accessors or save()
124
- # - Avoids default value conflicts with nil-skipping serialization
116
+ # 3. **Positional Arguments** (Legacy): Field assignment by definition order
117
+ # Example: Customer.new("cust_123", "John", "john@example.com")
125
118
  #
126
- # Note: We iterate over self.class.fields (not kwargs) to ensure only
127
- # defined fields are set, preventing typos from creating undefined attributes.
119
+ # 4. **No Arguments**: Object created with all fields as nil
128
120
  #
129
- if kwargs.any?
121
+ if args.size == 1 && kwargs.empty?
122
+ id_field = self.class.identifier_field
123
+ send(:"#{id_field}=", args.first)
124
+ elsif kwargs.any?
130
125
  initialize_with_keyword_args(**kwargs)
131
126
  elsif args.any?
132
127
  initialize_with_positional_args(*args)
133
128
  else
134
- Familia.trace :INITIALIZE, dbclient, "#{self.class} initialized with no arguments", caller(1..1) if Familia.debug?
135
- # Default values are intentionally NOT set here to:
136
- # - Maintain Database memory efficiency (only store non-nil values)
137
- # - Avoid conflicts with nil-skipping serialization logic
138
- # - Preserve consistent exists? behavior (empty vs default-filled objects)
139
- # - Keep initialization lightweight for unused fields
129
+ Familia.trace :INITIALIZE, dbclient, "#{self.class} initialized with no arguments", caller(1..1) if Familia.debug?
130
+ # Default values are intentionally NOT set here
140
131
  end
141
132
 
142
133
  # Implementing classes can define an init method to do any
@@ -2,7 +2,5 @@
2
2
 
3
3
  module Familia
4
4
  # Version information for the Familia
5
- unless defined?(Familia::VERSION)
6
- VERSION = '2.0.0.pre7'
7
- end
5
+ VERSION = '2.0.0.pre10'.freeze unless defined?(Familia::VERSION)
8
6
  end
data/setup.cfg ADDED
@@ -0,0 +1,12 @@
1
+ [scriv]
2
+ format = md
3
+ categories = Added, Changed, Deprecated, Removed, Fixed, Security, Documentation, AI Assistance
4
+ version_scheme = semver
5
+ entry_title_template = [{{ version }}] - {{ date }}
6
+ fragment_directory = changelog.d/fragments
7
+ template_file = changelog.d/template.md.j2
8
+ output_file = CHANGELOG.md
9
+ main_branches = main, develop
10
+ md_header_level = 2
11
+ end_marker = scriv-end-here
12
+ start_marker = scriv-insert-here
@@ -58,7 +58,7 @@ begin
58
58
  rescue Familia::NotConnected => e
59
59
  e.message.include?('No client for')
60
60
  end
61
- # > true
61
+ #=> true
62
62
 
63
63
  ## KeyNotFoundError stores key
64
64
  begin
@@ -1,6 +1,6 @@
1
1
  # try/features/encrypted_fields_core_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../helpers/test_helpers'
4
4
  require 'base64'
5
5
 
6
6
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Test constants will be redefined in each test since variables don't persist
4
4
 
5
- require_relative '../helpers/test_helpers'
5
+ require_relative '../../helpers/test_helpers'
6
6
  require 'base64'
7
7
 
8
8
 
@@ -3,7 +3,7 @@
3
3
  # Security tests for the no-cache encryption strategy
4
4
  # These tests verify that we maintain security properties by NOT caching derived keys
5
5
 
6
- require_relative '../helpers/test_helpers'
6
+ require_relative '../../helpers/test_helpers'
7
7
 
8
8
  test_keys = {
9
9
  v1: Base64.strict_encode64('a' * 32),
@@ -1,6 +1,6 @@
1
1
  # try/features/encrypted_fields_security_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../helpers/test_helpers'
4
4
  require 'base64'
5
5
 
6
6
  # Define all test classes up front to avoid tryouts retry conflicts
@@ -1,6 +1,6 @@
1
1
  # try/features/expiration_try.rb
2
2
 
3
- require_relative '../helpers/test_helpers'
3
+ require_relative '../../helpers/test_helpers'
4
4
 
5
5
  Familia.debug = false
6
6