familia 2.0.0.pre25 → 2.0.0
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/.gitignore +1 -0
- data/CHANGELOG.rst +69 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -2
- data/README.md +1 -3
- data/docs/guides/feature-encrypted-fields.md +1 -1
- data/docs/guides/feature-expiration.md +1 -1
- data/docs/guides/feature-quantization.md +1 -1
- data/docs/overview.md +7 -7
- data/docs/reference/api-technical.md +103 -7
- data/familia.gemspec +1 -2
- data/lib/familia/data_type/types/hashkey.rb +238 -0
- data/lib/familia/data_type/types/listkey.rb +110 -4
- data/lib/familia/data_type/types/sorted_set.rb +365 -0
- data/lib/familia/data_type/types/stringkey.rb +139 -0
- data/lib/familia/data_type/types/unsorted_set.rb +122 -2
- data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +2 -1
- data/lib/familia/features/relationships/participation/through_model_operations.rb +4 -3
- data/lib/familia/features/relationships/participation.rb +6 -6
- data/lib/familia/horreum/management.rb +29 -0
- data/lib/familia/version.rb +1 -1
- data/try/features/relationships/prefix_vs_config_name_try.rb +418 -0
- metadata +3 -27
- data/docs/migrating/v2.0.0-pre.md +0 -84
- data/docs/migrating/v2.0.0-pre11.md +0 -253
- data/docs/migrating/v2.0.0-pre12.md +0 -306
- data/docs/migrating/v2.0.0-pre13.md +0 -95
- data/docs/migrating/v2.0.0-pre14.md +0 -37
- data/docs/migrating/v2.0.0-pre18.md +0 -58
- data/docs/migrating/v2.0.0-pre19.md +0 -197
- data/docs/migrating/v2.0.0-pre22.md +0 -241
- data/docs/migrating/v2.0.0-pre5.md +0 -131
- data/docs/migrating/v2.0.0-pre6.md +0 -154
- data/docs/migrating/v2.0.0-pre7.md +0 -222
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: familia
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.0
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Delano Mandelbaum
|
|
@@ -9,20 +9,6 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: benchmark
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - "~>"
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.4'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - "~>"
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.4'
|
|
26
12
|
- !ruby/object:Gem::Dependency
|
|
27
13
|
name: concurrent-ruby
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -194,17 +180,6 @@ files:
|
|
|
194
180
|
- docs/guides/thread-safety-monitoring.md
|
|
195
181
|
- docs/guides/time-literals.md
|
|
196
182
|
- docs/migrating/.gitignore
|
|
197
|
-
- docs/migrating/v2.0.0-pre.md
|
|
198
|
-
- docs/migrating/v2.0.0-pre11.md
|
|
199
|
-
- docs/migrating/v2.0.0-pre12.md
|
|
200
|
-
- docs/migrating/v2.0.0-pre13.md
|
|
201
|
-
- docs/migrating/v2.0.0-pre14.md
|
|
202
|
-
- docs/migrating/v2.0.0-pre18.md
|
|
203
|
-
- docs/migrating/v2.0.0-pre19.md
|
|
204
|
-
- docs/migrating/v2.0.0-pre22.md
|
|
205
|
-
- docs/migrating/v2.0.0-pre5.md
|
|
206
|
-
- docs/migrating/v2.0.0-pre6.md
|
|
207
|
-
- docs/migrating/v2.0.0-pre7.md
|
|
208
183
|
- docs/overview.md
|
|
209
184
|
- docs/qodo-merge-compliance.md
|
|
210
185
|
- docs/reference/api-technical.md
|
|
@@ -377,6 +352,7 @@ files:
|
|
|
377
352
|
- try/features/relationships/participation_target_class_resolution_try.rb
|
|
378
353
|
- try/features/relationships/participation_through_try.rb
|
|
379
354
|
- try/features/relationships/participation_unresolved_target_try.rb
|
|
355
|
+
- try/features/relationships/prefix_vs_config_name_try.rb
|
|
380
356
|
- try/features/relationships/relationships_api_changes_try.rb
|
|
381
357
|
- try/features/relationships/relationships_edge_cases_try.rb
|
|
382
358
|
- try/features/relationships/relationships_performance_minimal_try.rb
|
|
@@ -556,7 +532,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
556
532
|
requirements:
|
|
557
533
|
- - ">="
|
|
558
534
|
- !ruby/object:Gem::Version
|
|
559
|
-
version: 3.
|
|
535
|
+
version: '3.2'
|
|
560
536
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
561
537
|
requirements:
|
|
562
538
|
- - ">="
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# Migrating Guide: v1.x to v2.0.0-pre
|
|
2
|
-
|
|
3
|
-
This guide covers migrating from Familia v1.x to the v2.0.0-pre foundation release.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
The v2.0.0-pre series represents a major modernization of Familia with:
|
|
8
|
-
- Complete API redesign for clarity and consistency
|
|
9
|
-
- Valkey compatibility alongside Valkey/Redis support
|
|
10
|
-
- Ruby 3.4+ modernization with improved thread safety
|
|
11
|
-
- New connection pooling architecture
|
|
12
|
-
|
|
13
|
-
## Step-by-Step Migration
|
|
14
|
-
|
|
15
|
-
### 1. Update Connection Configuration
|
|
16
|
-
|
|
17
|
-
**Before (v1.x):**
|
|
18
|
-
```ruby
|
|
19
|
-
Familia.connect('redis://localhost:6379/0')
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
**After (v2.0.0-pre):**
|
|
23
|
-
```ruby
|
|
24
|
-
Familia.configure do |config|
|
|
25
|
-
config.redis_uri = 'redis://localhost:6379/0'
|
|
26
|
-
config.connection_pool = true
|
|
27
|
-
end
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### 2. Migrate Identifier Declarations
|
|
31
|
-
|
|
32
|
-
**Before (v1.x):**
|
|
33
|
-
```ruby
|
|
34
|
-
class User < Familia::Base
|
|
35
|
-
identifier :user_id
|
|
36
|
-
end
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
**After (v2.0.0-pre):**
|
|
40
|
-
```ruby
|
|
41
|
-
class User < Familia::Horreum
|
|
42
|
-
identifier_field :user_id
|
|
43
|
-
end
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### 3. Update Feature Activations
|
|
47
|
-
|
|
48
|
-
**Before (v1.x):**
|
|
49
|
-
```ruby
|
|
50
|
-
class User < Familia::Base
|
|
51
|
-
include Familia::Features::Expiration
|
|
52
|
-
end
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
**After (v2.0.0-pre):**
|
|
56
|
-
```ruby
|
|
57
|
-
class User < Familia::Horreum
|
|
58
|
-
feature :expiration
|
|
59
|
-
end
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### 4. Review Method Calls
|
|
63
|
-
|
|
64
|
-
Several methods were renamed for consistency:
|
|
65
|
-
|
|
66
|
-
| v1.x Method | v2.0.0-pre Method | Notes |
|
|
67
|
-
|-------------|------------------|-------|
|
|
68
|
-
| `delete` | `destroy` | More semantic naming |
|
|
69
|
-
| `exists` | `exists?` | Ruby predicate convention |
|
|
70
|
-
| `dump` | `serialize` | Clearer intent |
|
|
71
|
-
|
|
72
|
-
## Breaking Changes
|
|
73
|
-
|
|
74
|
-
- `Familia::Base` replaced by `Familia::Horreum`
|
|
75
|
-
- Connection configuration moved to block-based setup
|
|
76
|
-
- Feature inclusion changed from `include` to `feature` declarations
|
|
77
|
-
- Several method names updated for consistency
|
|
78
|
-
|
|
79
|
-
## Next Steps
|
|
80
|
-
|
|
81
|
-
After completing the foundation migration:
|
|
82
|
-
1. Review [Security Feature Adoption](v2.0.0-pre5.md) for encrypted fields
|
|
83
|
-
2. See [Architecture Migration](v2.0.0-pre6.md) for persistence improvements
|
|
84
|
-
3. Explore [Relationships Migration](v2.0.0-pre7.md) for the new relationship system
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
# Migrating Guide: v2.0.0-pre11
|
|
2
|
-
|
|
3
|
-
This version introduces significant improvements to Familia's feature system, making it easier to organize and use features across complex projects.
|
|
4
|
-
|
|
5
|
-
## Enhanced Feature System
|
|
6
|
-
|
|
7
|
-
### Model-Specific Feature Registration
|
|
8
|
-
|
|
9
|
-
Previously, all features were registered globally. Now you can register features specific to individual model classes, allowing for better organization and namespace management.
|
|
10
|
-
|
|
11
|
-
#### Before
|
|
12
|
-
```ruby
|
|
13
|
-
# Global feature registration only
|
|
14
|
-
module MyProjectFeature
|
|
15
|
-
# Feature implementation
|
|
16
|
-
end
|
|
17
|
-
Familia::Base.add_feature MyProjectFeature, :my_project_feature
|
|
18
|
-
|
|
19
|
-
class Customer < Familia::Horreum
|
|
20
|
-
feature :my_project_feature
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
class Session < Familia::Horreum
|
|
24
|
-
feature :my_project_feature # Same global feature
|
|
25
|
-
end
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
#### After
|
|
29
|
-
```ruby
|
|
30
|
-
# Model-specific feature registration
|
|
31
|
-
module CustomerSpecificFeature
|
|
32
|
-
# Feature implementation
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Register feature only for Customer and its subclasses
|
|
36
|
-
Customer.add_feature CustomerSpecificFeature, :customer_specific
|
|
37
|
-
|
|
38
|
-
class Customer < Familia::Horreum
|
|
39
|
-
feature :customer_specific # Available via Customer's registry
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
class PremiumCustomer < Customer
|
|
43
|
-
feature :customer_specific # Inherited via ancestry chain
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
class Session < Familia::Horreum
|
|
47
|
-
# feature :customer_specific # Not available - would raise error
|
|
48
|
-
end
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
**Benefits:**
|
|
52
|
-
- Features can have the same name across different model hierarchies
|
|
53
|
-
- Standardized naming: `deprecated_fields.rb` instead of `customer_deprecated_fields.rb`
|
|
54
|
-
- Natural inheritance through Ruby's class hierarchy
|
|
55
|
-
|
|
56
|
-
## SafeDump DSL Improvements
|
|
57
|
-
|
|
58
|
-
The new DSL replaces the brittle `@safe_dump_fields` class instance variable pattern with clean, explicit methods.
|
|
59
|
-
|
|
60
|
-
### Before
|
|
61
|
-
```ruby
|
|
62
|
-
class Customer < Familia::Horreum
|
|
63
|
-
feature :safe_dump
|
|
64
|
-
|
|
65
|
-
# Brittle - hard to move to feature modules, confusing syntax
|
|
66
|
-
@safe_dump_fields = [
|
|
67
|
-
:custid,
|
|
68
|
-
:email,
|
|
69
|
-
{ active: ->(obj) { obj.active? } },
|
|
70
|
-
{ display_name: ->(obj) { "#{obj.name} (#{obj.custid})" } }
|
|
71
|
-
]
|
|
72
|
-
end
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### After
|
|
76
|
-
```ruby
|
|
77
|
-
class Customer < Familia::Horreum
|
|
78
|
-
feature :safe_dump
|
|
79
|
-
|
|
80
|
-
# Clean DSL - easy to understand and organize
|
|
81
|
-
safe_dump_field :custid
|
|
82
|
-
safe_dump_field :email
|
|
83
|
-
safe_dump_field :active, ->(obj) { obj.active? }
|
|
84
|
-
safe_dump_field :display_name, ->(obj) { "#{obj.name} (#{obj.custid})" }
|
|
85
|
-
|
|
86
|
-
# Or define multiple fields at once
|
|
87
|
-
safe_dump_fields :created, :updated, { status: ->(obj) { obj.role } }
|
|
88
|
-
end
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**New methods available:**
|
|
92
|
-
- `safe_dump_field(name, callable = nil)` - Define a single field
|
|
93
|
-
- `safe_dump_fields(*fields)` - Define multiple fields or get field names
|
|
94
|
-
- `safe_dump_field_names` - Get array of field names
|
|
95
|
-
- `safe_dump_field_map` - Get the internal callable map
|
|
96
|
-
|
|
97
|
-
**Backward Compatibility:**
|
|
98
|
-
- `set_safe_dump_fields(*fields)` - Legacy setter method (still works)
|
|
99
|
-
- The old `@safe_dump_fields` pattern is no longer supported
|
|
100
|
-
|
|
101
|
-
## Auto-loading Features
|
|
102
|
-
|
|
103
|
-
### Before: Manual Loading
|
|
104
|
-
```ruby
|
|
105
|
-
# customer/features.rb
|
|
106
|
-
|
|
107
|
-
# Manual feature loading (copied from Familia)
|
|
108
|
-
features_dir = File.join(__dir__, 'features')
|
|
109
|
-
if Dir.exist?(features_dir)
|
|
110
|
-
Dir.glob(File.join(features_dir, '*.rb')).each do |feature_file|
|
|
111
|
-
require_relative feature_file
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
class Customer < Familia::Horreum
|
|
116
|
-
# Features now available for use
|
|
117
|
-
feature :deprecated_fields
|
|
118
|
-
end
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### After: Automatic Loading
|
|
122
|
-
```ruby
|
|
123
|
-
# customer/features.rb
|
|
124
|
-
|
|
125
|
-
class Customer < Familia::Horreum
|
|
126
|
-
module Features
|
|
127
|
-
include Familia::Features::Autoloader
|
|
128
|
-
# Automatically discovers and loads all *.rb files from customer/features/
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
class Customer < Familia::Horreum
|
|
133
|
-
# Features automatically loaded and available
|
|
134
|
-
feature :deprecated_fields
|
|
135
|
-
end
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
**Directory structure this enables:**
|
|
139
|
-
```
|
|
140
|
-
models/
|
|
141
|
-
├── customer/
|
|
142
|
-
│ ├── features/
|
|
143
|
-
│ │ ├── deprecated_fields.rb # Standardized names!
|
|
144
|
-
│ │ ├── legacy_support.rb
|
|
145
|
-
│ │ └── stripe_integration.rb
|
|
146
|
-
│ └── features.rb # Include Autoloader here
|
|
147
|
-
├── session/
|
|
148
|
-
│ ├── features/
|
|
149
|
-
│ │ ├── deprecated_fields.rb # Same name, different implementation
|
|
150
|
-
│ │ └── expiration_hooks.rb
|
|
151
|
-
│ └── features.rb
|
|
152
|
-
└── customer.rb
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## Field Definitions in Feature Modules
|
|
156
|
-
|
|
157
|
-
Feature modules can now define fields directly in their `ClassMethods` modules. When a class extends the module, the field definitions execute in the extending class's context.
|
|
158
|
-
|
|
159
|
-
### Example
|
|
160
|
-
```ruby
|
|
161
|
-
# features/common_fields.rb
|
|
162
|
-
|
|
163
|
-
module CommonFields
|
|
164
|
-
def self.included(base)
|
|
165
|
-
base.extend ClassMethods
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
module ClassMethods
|
|
169
|
-
# These field calls execute in the extending class's context
|
|
170
|
-
field :created
|
|
171
|
-
field :updated
|
|
172
|
-
field :version
|
|
173
|
-
|
|
174
|
-
def touch_updated
|
|
175
|
-
self.updated = Familia.now.to_i
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
Familia::Base.add_feature self, :common_fields
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# Usage
|
|
183
|
-
class Customer < Familia::Horreum
|
|
184
|
-
feature :common_fields
|
|
185
|
-
# Now has :created, :updated, :version fields and touch_updated class method
|
|
186
|
-
end
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
## Migration Steps
|
|
190
|
-
|
|
191
|
-
### 1. Update SafeDump Usage
|
|
192
|
-
Replace all `@safe_dump_fields` definitions with the new DSL:
|
|
193
|
-
|
|
194
|
-
```ruby
|
|
195
|
-
# Find and replace pattern:
|
|
196
|
-
# Old: @safe_dump_fields = [:field1, :field2, { field3: ->(obj) { ... } }]
|
|
197
|
-
# New: safe_dump_fields :field1, :field2, { field3: ->(obj) { ... } }
|
|
198
|
-
|
|
199
|
-
# Or use individual field definitions for better readability:
|
|
200
|
-
safe_dump_field :field1
|
|
201
|
-
safe_dump_field :field2
|
|
202
|
-
safe_dump_field :field3, ->(obj) { ... }
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### 2. UnsortedSet Up Auto-loading (Optional)
|
|
206
|
-
If you have project-specific features, set up auto-loading:
|
|
207
|
-
|
|
208
|
-
```ruby
|
|
209
|
-
# Create: models/[model_name]/features.rb
|
|
210
|
-
module YourProject
|
|
211
|
-
class ModelName < Familia::Horreum
|
|
212
|
-
module Features
|
|
213
|
-
include Familia::Features::Autoloader
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
# Require this file before your model definitions
|
|
219
|
-
require_relative 'model_name/features'
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### 3. Organize Features by Model (Optional)
|
|
223
|
-
Consider reorganizing shared feature names by model:
|
|
224
|
-
|
|
225
|
-
```ruby
|
|
226
|
-
# Before: features/customer_deprecated_fields.rb
|
|
227
|
-
# After: models/customer/features/deprecated_fields.rb
|
|
228
|
-
|
|
229
|
-
# This allows multiple models to have their own deprecated_fields.rb
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### 4. Test Your Changes
|
|
233
|
-
Run your test suite to ensure all SafeDump functionality works correctly:
|
|
234
|
-
|
|
235
|
-
```ruby
|
|
236
|
-
# Verify SafeDump DSL works
|
|
237
|
-
model = YourModel.new(field1: 'value')
|
|
238
|
-
result = model.safe_dump
|
|
239
|
-
puts result.keys # Should include your defined fields
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
## Breaking Changes
|
|
243
|
-
|
|
244
|
-
1. **`@safe_dump_fields` no longer supported** - Must migrate to DSL methods
|
|
245
|
-
2. **SafeDump field order** - Fields are now returned in definition order via Hash keys (Ruby 1.9+ behavior)
|
|
246
|
-
|
|
247
|
-
## New Capabilities Unlocked
|
|
248
|
-
|
|
249
|
-
1. **Standardized feature names** across different models
|
|
250
|
-
2. **Cleaner SafeDump definitions** that can be easily moved to feature modules
|
|
251
|
-
3. **Automatic feature discovery** for better project organization
|
|
252
|
-
4. **Model-specific feature registries** for better namespace management
|
|
253
|
-
5. **Field definitions in feature modules** for shared functionality
|
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
# Migrating Guide: v2.0.0-pre12
|
|
2
|
-
|
|
3
|
-
This version introduces significant security improvements to Familia's identifier system, including verifiable identifiers with HMAC signatures, scoped identifier namespaces, and hardened external identifier derivation to prevent potential security vulnerabilities.
|
|
4
|
-
|
|
5
|
-
## VerifiableIdentifier Feature
|
|
6
|
-
|
|
7
|
-
### Overview
|
|
8
|
-
|
|
9
|
-
The new `Familia::VerifiableIdentifier` module allows applications to create and verify identifiers with embedded HMAC signatures. This enables stateless confirmation that an identifier was generated by your application, preventing forged IDs from malicious sources.
|
|
10
|
-
|
|
11
|
-
### Basic Usage
|
|
12
|
-
|
|
13
|
-
```ruby
|
|
14
|
-
class Customer < Familia::Horreum
|
|
15
|
-
feature :verifiable_identifier
|
|
16
|
-
|
|
17
|
-
# Required: UnsortedSet the HMAC secret (do this once in your app initialization)
|
|
18
|
-
# Generate with: SecureRandom.hex(64)
|
|
19
|
-
ENV['VERIFIABLE_ID_HMAC_SECRET'] = 'your_64_character_hex_secret'
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Generate a verifiable identifier
|
|
23
|
-
customer = Customer.new
|
|
24
|
-
verifiable_id = customer.generate_verifiable_id
|
|
25
|
-
# => "cust_1234567890abcdef_a1b2c3d4e5f6789..."
|
|
26
|
-
|
|
27
|
-
# Verify the identifier later (stateless verification)
|
|
28
|
-
if Customer.verified_identifier?(verifiable_id)
|
|
29
|
-
# Identifier is valid and was generated by this application
|
|
30
|
-
original_id = Customer.extract_identifier(verifiable_id)
|
|
31
|
-
customer = Customer.new(original_id)
|
|
32
|
-
else
|
|
33
|
-
# Identifier is forged or corrupted
|
|
34
|
-
raise SecurityError, "Invalid identifier"
|
|
35
|
-
end
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Scoped VerifiableIdentifier
|
|
39
|
-
|
|
40
|
-
The new `scope` parameter enables cryptographically isolated identifier namespaces for multi-tenant, multi-domain, or multi-environment applications.
|
|
41
|
-
|
|
42
|
-
#### Before (Global Scope)
|
|
43
|
-
```ruby
|
|
44
|
-
# All identifiers share the same cryptographic space
|
|
45
|
-
admin_id = admin.generate_verifiable_id
|
|
46
|
-
user_id = user.generate_verifiable_id
|
|
47
|
-
|
|
48
|
-
# Risk: Cross-contamination between different contexts
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
#### After (Scoped Namespaces)
|
|
52
|
-
```ruby
|
|
53
|
-
# Production environment
|
|
54
|
-
prod_customer_id = customer.generate_verifiable_id(scope: 'production')
|
|
55
|
-
prod_admin_id = admin.generate_verifiable_id(scope: 'production:admin')
|
|
56
|
-
|
|
57
|
-
# Development environment
|
|
58
|
-
dev_customer_id = customer.generate_verifiable_id(scope: 'development')
|
|
59
|
-
|
|
60
|
-
# Multi-tenant application
|
|
61
|
-
tenant_a_id = user.generate_verifiable_id(scope: "tenant:#{tenant_a.id}")
|
|
62
|
-
tenant_b_id = user.generate_verifiable_id(scope: "tenant:#{tenant_b.id}")
|
|
63
|
-
|
|
64
|
-
# Verification requires matching scope
|
|
65
|
-
Customer.verified_identifier?(prod_customer_id, scope: 'production') # => true
|
|
66
|
-
Customer.verified_identifier?(prod_customer_id, scope: 'development') # => false
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**Scope Benefits:**
|
|
70
|
-
- **Multi-tenant isolation**: Tenant A cannot forge identifiers for Tenant B
|
|
71
|
-
- **Environment separation**: Production IDs cannot be used in development
|
|
72
|
-
- **Role-based security**: Admin scopes separate from user scopes
|
|
73
|
-
- **Full backward compatibility**: Existing code without scopes continues to work
|
|
74
|
-
|
|
75
|
-
### Key Management
|
|
76
|
-
|
|
77
|
-
#### Secure Secret Generation
|
|
78
|
-
```ruby
|
|
79
|
-
# Generate a cryptographically secure HMAC secret
|
|
80
|
-
require 'securerandom'
|
|
81
|
-
secret = SecureRandom.hex(64) # 512-bit secret
|
|
82
|
-
puts "VERIFIABLE_ID_HMAC_SECRET=#{secret}"
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
#### Environment Configuration
|
|
86
|
-
```ruby
|
|
87
|
-
# config/application.rb or equivalent
|
|
88
|
-
# UnsortedSet this BEFORE any VerifiableIdentifier usage
|
|
89
|
-
ENV['VERIFIABLE_ID_HMAC_SECRET'] = Rails.application.credentials.verifiable_id_secret
|
|
90
|
-
|
|
91
|
-
# Or configure programmatically
|
|
92
|
-
Familia::VerifiableIdentifier.hmac_secret = your_secret_string
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## ObjectIdentifier Feature Improvements
|
|
96
|
-
|
|
97
|
-
### Method Renaming
|
|
98
|
-
|
|
99
|
-
Method names have been updated for clarity and consistency:
|
|
100
|
-
|
|
101
|
-
#### Before
|
|
102
|
-
```ruby
|
|
103
|
-
customer = Customer.new
|
|
104
|
-
objid = customer.generate_objid # Unclear what this generates
|
|
105
|
-
extid = Customer.generate_extid(objid) # Less secure class method
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
#### After
|
|
109
|
-
```ruby
|
|
110
|
-
customer = Customer.new
|
|
111
|
-
objid = customer.generate_object_identifier # Clear: generates object ID
|
|
112
|
-
extid = customer.derive_external_identifier # Clear: derives from objid, instance method
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
**Migration:**
|
|
116
|
-
- Replace `generate_objid` → `generate_object_identifier`
|
|
117
|
-
- Replace `generate_external_identifier` → `derive_external_identifier`
|
|
118
|
-
- Remove usage of `generate_extid` (deprecated for security reasons)
|
|
119
|
-
|
|
120
|
-
### Provenance Tracking
|
|
121
|
-
|
|
122
|
-
ObjectIdentifier now tracks which generator was used for each identifier:
|
|
123
|
-
|
|
124
|
-
```ruby
|
|
125
|
-
class Customer < Familia::Horreum
|
|
126
|
-
feature :object_identifier
|
|
127
|
-
|
|
128
|
-
# Configure generator type
|
|
129
|
-
object_identifier_generator :uuid_v7 # or :uuid_v4, :hex, custom proc
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
customer = Customer.new
|
|
133
|
-
objid = customer.generate_object_identifier
|
|
134
|
-
|
|
135
|
-
# Provenance information available
|
|
136
|
-
puts customer.object_identifier_generator_type # => :uuid_v7
|
|
137
|
-
puts customer.objid_format # => :uuid (normalized format)
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
**Benefits:**
|
|
141
|
-
- **Security auditing**: Know which generator created each identifier
|
|
142
|
-
- **Format normalization**: Eliminates ambiguity between UUID and hex formats
|
|
143
|
-
- **Migration support**: Track mixed generator usage during transitions
|
|
144
|
-
|
|
145
|
-
## ExternalIdentifier Security Hardening
|
|
146
|
-
|
|
147
|
-
### Provenance Validation
|
|
148
|
-
|
|
149
|
-
ExternalIdentifier now validates that objid values come from the ObjectIdentifier feature before deriving external identifiers.
|
|
150
|
-
|
|
151
|
-
#### Before (Potential Security Risk)
|
|
152
|
-
```ruby
|
|
153
|
-
# Could derive external IDs from any string, including malicious input
|
|
154
|
-
extid = customer.derive_external_identifier("malicious_input")
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
#### After (Hardened)
|
|
158
|
-
```ruby
|
|
159
|
-
customer = Customer.new
|
|
160
|
-
customer.generate_object_identifier # Must generate objid first
|
|
161
|
-
|
|
162
|
-
# Only works with validated objid from ObjectIdentifier feature
|
|
163
|
-
extid = customer.derive_external_identifier # Secure: uses validated objid
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Improved Security Model
|
|
167
|
-
|
|
168
|
-
External identifiers are now derived using the internal objid as a seed for a new random value, rather than directly deriving from objid.
|
|
169
|
-
|
|
170
|
-
#### Before
|
|
171
|
-
```ruby
|
|
172
|
-
# Direct derivation could leak information about objid
|
|
173
|
-
extid = hash(objid) # Information leakage risk
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
#### After
|
|
177
|
-
```ruby
|
|
178
|
-
# objid used as seed for new random value
|
|
179
|
-
extid = secure_hash(objid + additional_entropy) # No information leakage
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Error Handling Improvements
|
|
183
|
-
|
|
184
|
-
External identifier now raises clear errors for invalid usage:
|
|
185
|
-
|
|
186
|
-
```ruby
|
|
187
|
-
class Customer < Familia::Horreum
|
|
188
|
-
feature :external_identifier # Missing: object_identifier dependency
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
customer = Customer.new
|
|
192
|
-
# Raises ExternalIdentifierError instead of returning nil
|
|
193
|
-
customer.derive_external_identifier
|
|
194
|
-
# => Familia::ExternalIdentifierError: Model does not have an objid field
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## Migration Steps
|
|
198
|
-
|
|
199
|
-
### 1. Update Method Names
|
|
200
|
-
|
|
201
|
-
Replace deprecated method names in your codebase:
|
|
202
|
-
|
|
203
|
-
```bash
|
|
204
|
-
# Search and replace patterns:
|
|
205
|
-
grep -r "generate_objid" --include="*.rb" .
|
|
206
|
-
# Replace with: generate_object_identifier
|
|
207
|
-
|
|
208
|
-
grep -r "generate_external_identifier" --include="*.rb" .
|
|
209
|
-
# Replace with: derive_external_identifier
|
|
210
|
-
|
|
211
|
-
grep -r "generate_extid" --include="*.rb" .
|
|
212
|
-
# Remove usage - use derive_external_identifier instead
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### 2. Add HMAC Secret for VerifiableIdentifier
|
|
216
|
-
|
|
217
|
-
If you plan to use VerifiableIdentifier:
|
|
218
|
-
|
|
219
|
-
```ruby
|
|
220
|
-
# Generate secret
|
|
221
|
-
require 'securerandom'
|
|
222
|
-
secret = SecureRandom.hex(64)
|
|
223
|
-
|
|
224
|
-
# Add to your environment configuration
|
|
225
|
-
# .env, Rails credentials, or similar
|
|
226
|
-
VERIFIABLE_ID_HMAC_SECRET=your_generated_secret
|
|
227
|
-
|
|
228
|
-
# Verify configuration
|
|
229
|
-
puts ENV['VERIFIABLE_ID_HMAC_SECRET']&.length # Should be 128 characters
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### 3. Update ExternalIdentifier Usage
|
|
233
|
-
|
|
234
|
-
Ensure proper dependency chain:
|
|
235
|
-
|
|
236
|
-
```ruby
|
|
237
|
-
class YourModel < Familia::Horreum
|
|
238
|
-
# Required: ObjectIdentifier must come before ExternalIdentifier
|
|
239
|
-
feature :object_identifier
|
|
240
|
-
feature :external_identifier
|
|
241
|
-
|
|
242
|
-
# Configure generator if needed
|
|
243
|
-
object_identifier_generator :uuid_v7
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
# Usage pattern
|
|
247
|
-
model = YourModel.new
|
|
248
|
-
model.generate_object_identifier # Generate objid first
|
|
249
|
-
extid = model.derive_external_identifier # Then derive external ID
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### 4. Review Security-Sensitive Code
|
|
253
|
-
|
|
254
|
-
Audit any code that processes identifiers from external sources:
|
|
255
|
-
|
|
256
|
-
```ruby
|
|
257
|
-
# Before: Potentially unsafe
|
|
258
|
-
def process_identifier(external_id)
|
|
259
|
-
# Could process forged identifiers
|
|
260
|
-
model = Model.find_by_external_id(external_id)
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
# After: With verification
|
|
264
|
-
def process_identifier(verifiable_id)
|
|
265
|
-
# Verify identifier authenticity first
|
|
266
|
-
unless Model.verified_identifier?(verifiable_id)
|
|
267
|
-
raise SecurityError, "Invalid identifier"
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
original_id = Model.extract_identifier(verifiable_id)
|
|
271
|
-
model = Model.new(original_id)
|
|
272
|
-
end
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
## Breaking Changes
|
|
276
|
-
|
|
277
|
-
1. **`generate_extid` removed** - Use instance-level `derive_external_identifier` instead
|
|
278
|
-
2. **ExternalIdentifier validation** - Now raises `ExternalIdentifierError` instead of returning `nil` for models without objid
|
|
279
|
-
3. **Method names changed** - `generate_objid` → `generate_object_identifier`, `generate_external_identifier` → `derive_external_identifier`
|
|
280
|
-
|
|
281
|
-
## New Security Capabilities
|
|
282
|
-
|
|
283
|
-
1. **Cryptographic identifier verification** - Prevent forged IDs with HMAC signatures
|
|
284
|
-
2. **Scoped namespaces** - Isolate identifiers by tenant, environment, or role
|
|
285
|
-
3. **Provenance tracking** - Know which generator created each identifier
|
|
286
|
-
4. **Information leakage prevention** - External IDs no longer directly expose internal IDs
|
|
287
|
-
5. **Input validation** - Clear error messages for invalid operations
|
|
288
|
-
|
|
289
|
-
## Testing Your Migration
|
|
290
|
-
|
|
291
|
-
```ruby
|
|
292
|
-
# Test ObjectIdentifier changes
|
|
293
|
-
model = YourModel.new
|
|
294
|
-
objid = model.generate_object_identifier
|
|
295
|
-
extid = model.derive_external_identifier
|
|
296
|
-
puts "Generator: #{model.object_identifier_generator_type}"
|
|
297
|
-
|
|
298
|
-
# Test VerifiableIdentifier (if using)
|
|
299
|
-
vid = model.generate_verifiable_id
|
|
300
|
-
puts "Verifiable: #{YourModel.verified_identifier?(vid)}"
|
|
301
|
-
|
|
302
|
-
# Test scoped identifiers (if using)
|
|
303
|
-
scoped_vid = model.generate_verifiable_id(scope: 'production')
|
|
304
|
-
puts "Scoped valid: #{YourModel.verified_identifier?(scoped_vid, scope: 'production')}"
|
|
305
|
-
puts "Wrong scope: #{YourModel.verified_identifier?(scoped_vid, scope: 'development')}"
|
|
306
|
-
```
|