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.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +57 -0
  3. data/.github/workflows/claude.yml +71 -0
  4. data/.gitignore +5 -1
  5. data/.rubocop.yml +3 -0
  6. data/CLAUDE.md +32 -10
  7. data/Gemfile +2 -2
  8. data/Gemfile.lock +4 -3
  9. data/docs/wiki/API-Reference.md +95 -18
  10. data/docs/wiki/Connection-Pooling-Guide.md +437 -0
  11. data/docs/wiki/Encrypted-Fields-Overview.md +40 -3
  12. data/docs/wiki/Expiration-Feature-Guide.md +596 -0
  13. data/docs/wiki/Feature-System-Guide.md +631 -0
  14. data/docs/wiki/Features-System-Developer-Guide.md +892 -0
  15. data/docs/wiki/Field-System-Guide.md +784 -0
  16. data/docs/wiki/Home.md +82 -15
  17. data/docs/wiki/Implementation-Guide.md +126 -33
  18. data/docs/wiki/Quantization-Feature-Guide.md +721 -0
  19. data/docs/wiki/Relationships-Guide.md +684 -0
  20. data/docs/wiki/Security-Model.md +65 -25
  21. data/docs/wiki/Transient-Fields-Guide.md +280 -0
  22. data/examples/bit_encoding_integration.rb +237 -0
  23. data/examples/redis_command_validation_example.rb +231 -0
  24. data/examples/relationships_basic.rb +273 -0
  25. data/lib/familia/base.rb +1 -1
  26. data/lib/familia/connection.rb +3 -3
  27. data/lib/familia/data_type/types/counter.rb +38 -0
  28. data/lib/familia/data_type/types/hashkey.rb +18 -0
  29. data/lib/familia/data_type/types/lock.rb +43 -0
  30. data/lib/familia/data_type/types/string.rb +9 -2
  31. data/lib/familia/data_type.rb +9 -6
  32. data/lib/familia/encryption/encrypted_data.rb +137 -0
  33. data/lib/familia/encryption/manager.rb +21 -4
  34. data/lib/familia/encryption/providers/aes_gcm_provider.rb +20 -0
  35. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +20 -0
  36. data/lib/familia/encryption.rb +1 -1
  37. data/lib/familia/errors.rb +17 -3
  38. data/lib/familia/features/encrypted_fields/concealed_string.rb +293 -0
  39. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +94 -26
  40. data/lib/familia/features/encrypted_fields.rb +413 -4
  41. data/lib/familia/features/expiration.rb +319 -33
  42. data/lib/familia/features/quantization.rb +385 -44
  43. data/lib/familia/features/relationships/cascading.rb +438 -0
  44. data/lib/familia/features/relationships/indexing.rb +370 -0
  45. data/lib/familia/features/relationships/membership.rb +503 -0
  46. data/lib/familia/features/relationships/permission_management.rb +264 -0
  47. data/lib/familia/features/relationships/querying.rb +620 -0
  48. data/lib/familia/features/relationships/redis_operations.rb +274 -0
  49. data/lib/familia/features/relationships/score_encoding.rb +442 -0
  50. data/lib/familia/features/relationships/tracking.rb +379 -0
  51. data/lib/familia/features/relationships.rb +466 -0
  52. data/lib/familia/features/safe_dump.rb +1 -1
  53. data/lib/familia/features/transient_fields/redacted_string.rb +1 -1
  54. data/lib/familia/features/transient_fields.rb +192 -10
  55. data/lib/familia/features.rb +2 -1
  56. data/lib/familia/field_type.rb +5 -2
  57. data/lib/familia/horreum/{connection.rb → core/connection.rb} +2 -8
  58. data/lib/familia/horreum/{database_commands.rb → core/database_commands.rb} +14 -3
  59. data/lib/familia/horreum/core/serialization.rb +535 -0
  60. data/lib/familia/horreum/{utils.rb → core/utils.rb} +0 -2
  61. data/lib/familia/horreum/core.rb +21 -0
  62. data/lib/familia/horreum/{settings.rb → shared/settings.rb} +0 -2
  63. data/lib/familia/horreum/{definition_methods.rb → subclass/definition.rb} +45 -29
  64. data/lib/familia/horreum/{management_methods.rb → subclass/management.rb} +9 -8
  65. data/lib/familia/horreum/{related_fields_management.rb → subclass/related_fields_management.rb} +15 -10
  66. data/lib/familia/horreum.rb +17 -17
  67. data/lib/familia/validation/command_recorder.rb +336 -0
  68. data/lib/familia/validation/expectations.rb +519 -0
  69. data/lib/familia/validation/test_helpers.rb +443 -0
  70. data/lib/familia/validation/validator.rb +412 -0
  71. data/lib/familia/validation.rb +140 -0
  72. data/lib/familia/version.rb +1 -1
  73. data/lib/familia.rb +1 -1
  74. data/try/core/create_method_try.rb +240 -0
  75. data/try/core/database_consistency_try.rb +299 -0
  76. data/try/core/errors_try.rb +25 -4
  77. data/try/core/familia_try.rb +1 -1
  78. data/try/core/persistence_operations_try.rb +297 -0
  79. data/try/data_types/counter_try.rb +93 -0
  80. data/try/data_types/lock_try.rb +133 -0
  81. data/try/debugging/debug_aad_process.rb +82 -0
  82. data/try/debugging/debug_concealed_internal.rb +59 -0
  83. data/try/debugging/debug_concealed_reveal.rb +61 -0
  84. data/try/debugging/debug_context_aad.rb +68 -0
  85. data/try/debugging/debug_context_simple.rb +80 -0
  86. data/try/debugging/debug_cross_context.rb +62 -0
  87. data/try/debugging/debug_database_load.rb +64 -0
  88. data/try/debugging/debug_encrypted_json_check.rb +53 -0
  89. data/try/debugging/debug_encrypted_json_step_by_step.rb +62 -0
  90. data/try/debugging/debug_exists_lifecycle.rb +54 -0
  91. data/try/debugging/debug_field_decrypt.rb +74 -0
  92. data/try/debugging/debug_fresh_cross_context.rb +73 -0
  93. data/try/debugging/debug_load_path.rb +66 -0
  94. data/try/debugging/debug_method_definition.rb +46 -0
  95. data/try/debugging/debug_method_resolution.rb +41 -0
  96. data/try/debugging/debug_minimal.rb +24 -0
  97. data/try/debugging/debug_provider.rb +68 -0
  98. data/try/debugging/debug_secure_behavior.rb +73 -0
  99. data/try/debugging/debug_string_class.rb +46 -0
  100. data/try/debugging/debug_test.rb +46 -0
  101. data/try/debugging/debug_test_design.rb +80 -0
  102. data/try/edge_cases/hash_symbolization_try.rb +1 -0
  103. data/try/edge_cases/reserved_keywords_try.rb +1 -0
  104. data/try/edge_cases/string_coercion_try.rb +2 -0
  105. data/try/encryption/encryption_core_try.rb +6 -4
  106. data/try/features/categorical_permissions_try.rb +515 -0
  107. data/try/features/encrypted_fields_core_try.rb +19 -11
  108. data/try/features/encrypted_fields_integration_try.rb +66 -70
  109. data/try/features/encrypted_fields_no_cache_security_try.rb +22 -8
  110. data/try/features/encrypted_fields_security_try.rb +151 -144
  111. data/try/features/encryption_fields/aad_protection_try.rb +108 -23
  112. data/try/features/encryption_fields/concealed_string_core_try.rb +253 -0
  113. data/try/features/encryption_fields/context_isolation_try.rb +30 -8
  114. data/try/features/encryption_fields/error_conditions_try.rb +6 -6
  115. data/try/features/encryption_fields/fresh_key_derivation_try.rb +20 -14
  116. data/try/features/encryption_fields/fresh_key_try.rb +27 -22
  117. data/try/features/encryption_fields/key_rotation_try.rb +16 -10
  118. data/try/features/encryption_fields/nonce_uniqueness_try.rb +15 -13
  119. data/try/features/encryption_fields/secure_by_default_behavior_try.rb +310 -0
  120. data/try/features/encryption_fields/thread_safety_try.rb +6 -6
  121. data/try/features/encryption_fields/universal_serialization_safety_try.rb +174 -0
  122. data/try/features/feature_dependencies_try.rb +3 -3
  123. data/try/features/relationships_edge_cases_try.rb +145 -0
  124. data/try/features/relationships_performance_minimal_try.rb +132 -0
  125. data/try/features/relationships_performance_simple_try.rb +155 -0
  126. data/try/features/relationships_performance_try.rb +420 -0
  127. data/try/features/relationships_performance_working_try.rb +144 -0
  128. data/try/features/relationships_try.rb +237 -0
  129. data/try/features/safe_dump_try.rb +3 -0
  130. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  131. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  132. data/try/features/transient_fields_core_try.rb +1 -1
  133. data/try/features/transient_fields_integration_try.rb +1 -1
  134. data/try/helpers/test_helpers.rb +26 -1
  135. data/try/horreum/base_try.rb +14 -8
  136. data/try/horreum/enhanced_conflict_handling_try.rb +3 -1
  137. data/try/horreum/initialization_try.rb +1 -1
  138. data/try/horreum/relations_try.rb +2 -2
  139. data/try/horreum/serialization_persistent_fields_try.rb +8 -8
  140. data/try/horreum/serialization_try.rb +39 -4
  141. data/try/models/customer_safe_dump_try.rb +1 -1
  142. data/try/models/customer_try.rb +1 -1
  143. data/try/validation/atomic_operations_try.rb.disabled +320 -0
  144. data/try/validation/command_validation_try.rb.disabled +207 -0
  145. data/try/validation/performance_validation_try.rb.disabled +324 -0
  146. data/try/validation/real_world_scenarios_try.rb.disabled +390 -0
  147. metadata +81 -12
  148. data/TEST_COVERAGE.md +0 -40
  149. data/lib/familia/features/relatable_objects.rb +0 -125
  150. data/lib/familia/horreum/serialization.rb +0 -473
  151. data/try/features/relatable_objects_try.rb +0 -220
@@ -2,19 +2,25 @@
2
2
 
3
3
  ## Cryptographic Design
4
4
 
5
+ ### Provider-Based Architecture
6
+
7
+ Familia uses a modular provider system that automatically selects the best available encryption algorithm:
8
+
5
9
  ### Encryption Algorithms
6
10
 
7
- **Primary (with libsodium):**
8
- - Algorithm: XChaCha20-Poly1305
9
- - Key Size: 256 bits
10
- - Nonce Size: 192 bits
11
- - Authentication Tag: 128 bits
11
+ **XChaCha20-Poly1305 Provider (Priority: 100)**
12
+ - Requires: `rbnacl` gem (libsodium bindings)
13
+ - Key Size: 256 bits (32 bytes)
14
+ - Nonce Size: 192 bits (24 bytes) - extended nonce space
15
+ - Authentication Tag: 128 bits (16 bytes)
16
+ - Key Derivation: BLAKE2b with personalization string
12
17
 
13
- **Fallback (with OpenSSL):**
14
- - Algorithm: AES-256-GCM
15
- - Key Size: 256 bits
16
- - IV Size: 96 bits
17
- - Authentication Tag: 128 bits
18
+ **AES-256-GCM Provider (Priority: 50)**
19
+ - Requires: OpenSSL (always available)
20
+ - Key Size: 256 bits (32 bytes)
21
+ - Nonce Size: 96 bits (12 bytes) - standard GCM nonce
22
+ - Authentication Tag: 128 bits (16 bytes)
23
+ - Key Derivation: HKDF-SHA256
18
24
 
19
25
  ### Key Derivation
20
26
 
@@ -26,19 +32,40 @@ Field Key = KDF(Master Key, Context)
26
32
  Where Context = "ClassName:field_name:record_identifier"
27
33
  ```
28
34
 
29
- **KDF Functions:**
30
- - Libsodium: BLAKE2b with personalization
31
- - OpenSSL: HKDF-SHA256
35
+ **Provider-Specific KDF:**
36
+ - **XChaCha20-Poly1305**: BLAKE2b with customizable personalization string
37
+ - **AES-256-GCM**: HKDF-SHA256 with salt and info parameters
38
+
39
+ The personalization string provides cryptographic domain separation:
40
+ ```ruby
41
+ Familia.configure do |config|
42
+ config.encryption_personalization = 'MyApp-2024' # Default: 'Familia'
43
+ end
44
+ ```
32
45
 
33
46
  ### Ciphertext Format
34
47
 
48
+ The encrypted data is stored as JSON with algorithm-specific fields:
49
+
50
+ **XChaCha20-Poly1305:**
35
51
  ```json
36
52
  {
37
- "library": "libsodium",
38
53
  "algorithm": "xchacha20poly1305",
39
- "nonce": "base64_encoded_nonce",
40
- "ciphertext": "base64_encoded_ciphertext",
41
- "key_version": "v1_2504"
54
+ "nonce": "base64_24_byte_nonce",
55
+ "ciphertext": "base64_encrypted_data",
56
+ "auth_tag": "base64_16_byte_tag",
57
+ "key_version": "v1"
58
+ }
59
+ ```
60
+
61
+ **AES-256-GCM:**
62
+ ```json
63
+ {
64
+ "algorithm": "aes-256-gcm",
65
+ "nonce": "base64_12_byte_iv",
66
+ "ciphertext": "base64_encrypted_data",
67
+ "auth_tag": "base64_16_byte_tag",
68
+ "key_version": "v1"
42
69
  }
43
70
  ```
44
71
 
@@ -97,15 +124,28 @@ vault.love_letter(passphrase_value: user_passphrase)
97
124
 
98
125
  ### Memory Safety
99
126
 
100
- **With libsodium:**
101
- - Automatic zeroing of sensitive memory
102
- - Constant-time comparisons
103
- - Protected memory pages when available
127
+ **⚠️ Critical Ruby Memory Limitations:**
128
+
129
+ Ruby provides **NO** memory safety guarantees for cryptographic secrets. This affects ALL providers:
130
+
131
+ - **No secure memory wiping**: Ruby cannot guarantee memory zeroing
132
+ - **GC copying**: Garbage collector may copy secrets before cleanup
133
+ - **String operations**: Every `.dup`, `+`, or interpolation creates uncontrolled copies
134
+ - **Memory dumps**: Secrets may persist in swap files or core dumps
135
+ - **Finalizer uncertainty**: `ObjectSpace.define_finalizer` timing is unpredictable
136
+
137
+ **Provider-Specific Mitigations:**
138
+
139
+ Both providers attempt best-effort memory clearing:
140
+ - Call `.clear` on sensitive strings after use
141
+ - Set variables to `nil` when done
142
+ - Use finalizers for cleanup (no guarantees)
104
143
 
105
- **Without libsodium:**
106
- - Warning logged about reduced security
107
- - Ruby GC may retain plaintext copies
108
- - Timing attacks theoretically possible
144
+ **Recommendation**: For production systems with high-security requirements, consider:
145
+ - Hardware Security Modules (HSMs)
146
+ - External key management services
147
+ - Languages with manual memory management (C, Rust)
148
+ - Cryptographic appliances with secure enclaves
109
149
 
110
150
  ### RedactedString
111
151
 
@@ -0,0 +1,280 @@
1
+ # Transient Fields Guide
2
+
3
+ ## Overview
4
+
5
+ Transient fields provide secure handling of sensitive runtime data that should never be persisted to Redis/Valkey. Unlike encrypted fields, transient fields exist only in memory and are automatically wrapped in `RedactedString` for security.
6
+
7
+ ## When to Use Transient Fields
8
+
9
+ Use transient fields for:
10
+ - API keys and tokens that change frequently
11
+ - Temporary passwords or passphrases
12
+ - Session-specific secrets
13
+ - Any sensitive data that should never touch persistent storage
14
+ - Debug or development secrets that need secure handling
15
+
16
+ ## Basic Usage
17
+
18
+ ### Define Transient Fields
19
+
20
+ ```ruby
21
+ class ApiClient < Familia::Horreum
22
+ feature :transient_fields
23
+
24
+ field :endpoint # Regular persistent field
25
+ transient_field :token # Transient field (not persisted)
26
+ transient_field :secret, as: :api_secret # Custom accessor name
27
+ end
28
+ ```
29
+
30
+ ### Working with Transient Fields
31
+
32
+ ```ruby
33
+ client = ApiClient.new(
34
+ endpoint: 'https://api.example.com',
35
+ token: ENV['API_TOKEN'],
36
+ secret: ENV['API_SECRET']
37
+ )
38
+
39
+ # Regular field persists
40
+ client.save
41
+ client.endpoint # => "https://api.example.com"
42
+
43
+ # Transient fields are RedactedString instances
44
+ puts client.token # => "[REDACTED]"
45
+
46
+ # Access the actual value safely
47
+ client.token.expose do |token|
48
+ response = HTTP.post(client.endpoint,
49
+ headers: { 'Authorization' => "Bearer #{token}" }
50
+ )
51
+ # Token value is only available within this block
52
+ end
53
+
54
+ # Explicit cleanup when done
55
+ client.token.clear!
56
+ ```
57
+
58
+ ## RedactedString Security
59
+
60
+ ### Automatic Wrapping
61
+
62
+ All transient field values are automatically wrapped in `RedactedString`:
63
+
64
+ ```ruby
65
+ client = ApiClient.new(token: 'secret123')
66
+ client.token.class # => RedactedString
67
+ ```
68
+
69
+ ### Safe Access Pattern
70
+
71
+ ```ruby
72
+ # ✅ Recommended: Use .expose block
73
+ client.token.expose do |token|
74
+ # Use token directly without creating copies
75
+ HTTP.auth("Bearer #{token}") # Safe
76
+ end
77
+
78
+ # ✅ Direct access (use carefully)
79
+ raw_token = client.token.value
80
+ # Remember to clear original source if needed
81
+
82
+ # ❌ Avoid: These create uncontrolled copies
83
+ token_copy = client.token.value.dup # Creates copy in memory
84
+ interpolated = "Bearer #{client.token}" # Creates copy via to_s
85
+ ```
86
+
87
+ ### Memory Management
88
+
89
+ ```ruby
90
+ # Clear individual fields
91
+ client.token.clear!
92
+
93
+ # Check if cleared
94
+ client.token.cleared? # => true
95
+
96
+ # Accessing cleared values raises error
97
+ client.token.value # => SecurityError: Value already cleared
98
+ ```
99
+
100
+ ## Advanced Features
101
+
102
+ ### Custom Accessor Names
103
+
104
+ ```ruby
105
+ class Service < Familia::Horreum
106
+ transient_field :api_key, as: :secret_key
107
+ end
108
+
109
+ service = Service.new(api_key: 'secret123')
110
+ service.secret_key.expose { |key| use_api_key(key) }
111
+ ```
112
+
113
+ ### Integration with Encrypted Fields
114
+
115
+ ```ruby
116
+ class SecureService < Familia::Horreum
117
+ feature :transient_fields
118
+
119
+ encrypted_field :long_term_secret # Persisted, encrypted
120
+ transient_field :session_token # Runtime only, not persisted
121
+ field :public_endpoint # Normal field
122
+ end
123
+
124
+ service = SecureService.new(
125
+ long_term_secret: 'stored encrypted in Redis',
126
+ session_token: 'temporary runtime secret',
127
+ public_endpoint: 'https://api.example.com'
128
+ )
129
+
130
+ service.save
131
+ # Only long_term_secret and public_endpoint are saved to Redis
132
+ # session_token exists only in memory
133
+ ```
134
+
135
+ ## RedactedString API Reference
136
+
137
+ ### Core Methods
138
+
139
+ ```ruby
140
+ # Create (usually automatic via transient_field)
141
+ secret = RedactedString.new('sensitive_value')
142
+
143
+ # Safe access
144
+ secret.expose { |value| use_value(value) }
145
+
146
+ # Direct access (use with caution)
147
+ value = secret.value
148
+
149
+ # Cleanup
150
+ secret.clear!
151
+
152
+ # Status
153
+ secret.cleared? # => true/false
154
+ ```
155
+
156
+ ### Security Methods
157
+
158
+ ```ruby
159
+ # Logging/debugging protection
160
+ puts secret.to_s # => "[REDACTED]"
161
+ puts secret.inspect # => "[REDACTED]"
162
+
163
+ # Equality (object identity only)
164
+ secret1 == secret2 # => false (unless same object)
165
+
166
+ # Hash (constant for all instances)
167
+ secret.hash # => Same for all RedactedString instances
168
+ ```
169
+
170
+ ## Security Considerations
171
+
172
+ ### Ruby Memory Limitations
173
+
174
+ **⚠️ Important**: Ruby provides no memory safety guarantees:
175
+
176
+ - **No secure wiping**: `.clear!` is best-effort only
177
+ - **GC copying**: Garbage collector may duplicate secrets
178
+ - **String operations**: Every manipulation creates copies
179
+ - **Memory persistence**: Secrets may remain in memory indefinitely
180
+
181
+ ### Best Practices
182
+
183
+ ```ruby
184
+ # ✅ Wrap immediately after input
185
+ password = RedactedString.new(params[:password])
186
+ params[:password] = nil # Clear original reference
187
+
188
+ # ✅ Use .expose for short operations
189
+ token.expose { |t| api_call(t) }
190
+
191
+ # ✅ Clear explicitly when done
192
+ token.clear!
193
+
194
+ # ✅ Avoid string operations that create copies
195
+ token.expose { |t| "Bearer #{t}" } # Creates copy
196
+ # Better: Pass token directly to methods that need it
197
+
198
+ # ❌ Don't pass RedactedString to logging
199
+ logger.info "Token: #{token}" # Still logs "[REDACTED]" but safer to avoid
200
+
201
+ # ❌ Don't store in instance variables outside field system
202
+ @raw_token = token.value # Creates uncontrolled copy
203
+ ```
204
+
205
+ ### Production Recommendations
206
+
207
+ For highly sensitive applications, consider:
208
+ - External secrets management (HashiCorp Vault, AWS Secrets Manager)
209
+ - Hardware Security Modules (HSMs)
210
+ - Languages with secure memory handling
211
+ - Encrypted swap and memory protection at OS level
212
+
213
+ ## Integration Examples
214
+
215
+ ### Rails Controller
216
+
217
+ ```ruby
218
+ class ApiController < ApplicationController
219
+ def authenticate
220
+ service = ApiService.new(
221
+ endpoint: params[:endpoint],
222
+ token: params[:token] # Auto-wrapped in RedactedString
223
+ )
224
+
225
+ result = service.token.expose do |token|
226
+ # Token only accessible within this block
227
+ ExternalAPI.authenticate(token)
228
+ end
229
+
230
+ # Clear token when request is done
231
+ service.token.clear!
232
+
233
+ render json: { status: result }
234
+ end
235
+ end
236
+ ```
237
+
238
+ ### Background Job
239
+
240
+ ```ruby
241
+ class ApiSyncJob
242
+ def perform(user_id, token)
243
+ user = User.find(user_id)
244
+
245
+ # Wrap external token securely
246
+ secure_token = RedactedString.new(token)
247
+ token.clear if token.respond_to?(:clear) # Clear original
248
+
249
+ client = ApiClient.new(token: secure_token)
250
+
251
+ begin
252
+ sync_data(client)
253
+ ensure
254
+ client.token.clear! # Always cleanup
255
+ end
256
+ end
257
+
258
+ private
259
+
260
+ def sync_data(client)
261
+ client.token.expose do |token|
262
+ # Use token for API calls
263
+ fetch_and_process_data(token)
264
+ end
265
+ end
266
+ end
267
+ ```
268
+
269
+ ## Comparison with Encrypted Fields
270
+
271
+ | Feature | Encrypted Fields | Transient Fields |
272
+ |---------|------------------|------------------|
273
+ | **Persistence** | Saved to Redis/Valkey | Memory only |
274
+ | **Encryption** | AES/XChaCha20 | None (not stored) |
275
+ | **Use Case** | Long-term secrets | Runtime secrets |
276
+ | **Access** | Automatic decrypt | RedactedString wrapper |
277
+ | **Performance** | Crypto overhead | No crypto operations |
278
+ | **Lifecycle** | Survives restarts | Cleared on restart |
279
+
280
+ Choose encrypted fields for data that must persist across sessions. Choose transient fields for sensitive runtime data that should never be stored.
@@ -0,0 +1,237 @@
1
+ # examples/bit_encoding_integration.rb
2
+ #
3
+ # Production Integration Example: Document Management System with Fine-Grained Permissions
4
+ #
5
+ # This example demonstrates how to use Familia's bit encoding permission system
6
+ # in a real-world document management scenario with sophisticated access control.
7
+
8
+ require_relative '../lib/familia'
9
+ require_relative '../lib/familia/features/relationships/score_encoding'
10
+ require_relative '../lib/familia/features/relationships/permission_management'
11
+
12
+ # Document Management System Classes
13
+ class User < Familia::Horreum
14
+ logical_database 14
15
+
16
+ identifier_field :user_id
17
+ field :user_id
18
+ field :email
19
+ field :name
20
+ field :role # admin, editor, viewer, guest
21
+ field :created_at
22
+
23
+ sorted_set :documents # Documents this user can access
24
+ sorted_set :recent_activity # Recent document access
25
+ end
26
+
27
+ class Document < Familia::Horreum
28
+ include Familia::Features::Relationships::PermissionManagement
29
+
30
+ logical_database 14
31
+
32
+ # Enable fine-grained permission tracking
33
+ permission_tracking :user_permissions
34
+
35
+ identifier_field :doc_id
36
+ field :doc_id
37
+ field :title
38
+ field :owner_id
39
+ field :content
40
+ field :created_at
41
+ field :updated_at
42
+ field :document_type # public, private, confidential
43
+
44
+ sorted_set :collaborators # Users with access to this document
45
+ list :audit_log # Track permission changes and access
46
+
47
+ # Add document to user's collection with specific permissions
48
+ def share_with_user(user, *permissions)
49
+ permissions = [:read] if permissions.empty?
50
+
51
+ # Create time-based score with permissions encoded
52
+ timestamp = updated_at || Time.now
53
+ score = Familia::Features::Relationships::ScoreEncoding.encode_score(timestamp, permissions)
54
+
55
+ # Add to user's document list
56
+ user.documents.add(score, doc_id)
57
+
58
+ # Add user to document's collaborator list
59
+ collaborators.add(score, user.user_id)
60
+
61
+ # Grant permissions via permission management
62
+ grant(user, *permissions)
63
+
64
+ # Log the permission grant
65
+ log_entry = "#{Time.now.iso8601}: Granted #{permissions.join(', ')} to #{user.email}"
66
+ audit_log.push(log_entry)
67
+ end
68
+
69
+ # Remove user access
70
+ def revoke_access(user)
71
+ user.documents.zrem(doc_id)
72
+ collaborators.zrem(user.user_id)
73
+ revoke(user, :read, :write, :edit, :delete, :configure, :transfer, :admin)
74
+
75
+ log_entry = "#{Time.now.iso8601}: Revoked all access from #{user.email}"
76
+ audit_log.push(log_entry)
77
+ end
78
+
79
+ # Get users with specific permission level or higher
80
+ def users_with_permission(*required_permissions)
81
+ all_permissions.select do |user_id, user_perms|
82
+ required_permissions.all? { |perm| user_perms.include?(perm) }
83
+ end.keys
84
+ end
85
+
86
+ # Advanced: Get document access history for analytics
87
+ def access_analytics(days_back = 30)
88
+ start_time = Time.now - (days_back * 24 * 60 * 60)
89
+ end_time = Time.now
90
+
91
+ # Use score range to get recent access
92
+ range = Familia::Features::Relationships::ScoreEncoding.score_range(
93
+ start_time,
94
+ end_time,
95
+ min_permissions: [:read]
96
+ )
97
+
98
+ # Get collaborators active in time range
99
+ active_users = collaborators.rangebyscore(*range)
100
+
101
+ {
102
+ active_users: active_users,
103
+ total_collaborators: collaborators.size,
104
+ permission_breakdown: all_permissions,
105
+ audit_entries: audit_log.range(0, 50)
106
+ }
107
+ end
108
+ end
109
+
110
+ # Document Management Service - Business Logic Layer
111
+ class DocumentService
112
+ # Permission role definitions matching business needs
113
+ ROLE_PERMISSIONS = {
114
+ guest: [:read],
115
+ viewer: [:read],
116
+ commenter: [:read, :append],
117
+ editor: [:read, :write, :edit],
118
+ reviewer: [:read, :write, :edit, :delete],
119
+ admin: [:read, :write, :edit, :delete, :configure, :transfer, :admin]
120
+ }.freeze
121
+
122
+ def self.create_document(owner, title, content, doc_type = 'private')
123
+ doc = Document.new(
124
+ doc_id: "doc_#{Time.now.to_i}_#{rand(1000)}",
125
+ title: title,
126
+ content: content,
127
+ owner_id: owner.user_id,
128
+ document_type: doc_type,
129
+ created_at: Time.now,
130
+ updated_at: Time.now
131
+ )
132
+
133
+ # Owner gets full admin access
134
+ doc.share_with_user(owner, *ROLE_PERMISSIONS[:admin])
135
+ doc
136
+ end
137
+
138
+ def self.share_document(document, user, role)
139
+ permissions = ROLE_PERMISSIONS[role] || ROLE_PERMISSIONS[:viewer]
140
+ document.share_with_user(user, *permissions)
141
+ end
142
+
143
+ def self.can_user_perform?(user, document, action)
144
+ case action
145
+ when :view, :read
146
+ document.can?(user, :read)
147
+ when :comment, :append
148
+ document.can?(user, :read, :append)
149
+ when :edit, :modify
150
+ document.can?(user, :read, :write, :edit)
151
+ when :delete, :remove
152
+ document.can?(user, :delete)
153
+ when :share, :configure
154
+ document.can?(user, :configure)
155
+ when :transfer_ownership
156
+ document.can?(user, :admin)
157
+ else
158
+ false
159
+ end
160
+ end
161
+
162
+ def self.bulk_permission_update(documents, users, role)
163
+ permissions = ROLE_PERMISSIONS[role]
164
+
165
+ documents.each do |doc|
166
+ users.each do |user|
167
+ doc.revoke_access(user) # Clear existing
168
+ doc.share_with_user(user, *permissions) if permissions
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ # Example Usage and Demonstration
175
+ if __FILE__ == $0
176
+ puts "🚀 Familia Bit Encoding Integration Example"
177
+ puts "=" * 50
178
+
179
+ # Create users
180
+ alice = User.new(user_id: 'alice', email: 'alice@company.com', name: 'Alice Smith', role: 'admin')
181
+ bob = User.new(user_id: 'bob', email: 'bob@company.com', name: 'Bob Jones', role: 'editor')
182
+ charlie = User.new(user_id: 'charlie', email: 'charlie@company.com', name: 'Charlie Brown', role: 'viewer')
183
+
184
+ # Create documents
185
+ doc1 = DocumentService.create_document(alice, "Q4 Financial Report", "Confidential financial data...", 'confidential')
186
+ doc2 = DocumentService.create_document(alice, "Team Meeting Notes", "Weekly standup notes...", 'private')
187
+ doc3 = DocumentService.create_document(bob, "Project Proposal", "New feature proposal...", 'public')
188
+
189
+ # Share documents with different permission levels
190
+ puts "\n📄 Document Sharing:"
191
+ DocumentService.share_document(doc1, bob, :reviewer) # Bob can review financial report
192
+ DocumentService.share_document(doc1, charlie, :viewer) # Charlie can only view
193
+
194
+ DocumentService.share_document(doc2, bob, :editor) # Bob can edit meeting notes
195
+ DocumentService.share_document(doc2, charlie, :commenter) # Charlie can comment
196
+
197
+ DocumentService.share_document(doc3, alice, :admin) # Alice gets admin on Bob's doc
198
+ DocumentService.share_document(doc3, charlie, :editor) # Charlie can edit proposal
199
+
200
+ # Test permission checks
201
+ puts "\n🔐 Permission Testing:"
202
+ puts "Can Bob edit financial report? #{DocumentService.can_user_perform?(bob, doc1, :edit)}"
203
+ puts "Can Bob delete financial report? #{DocumentService.can_user_perform?(bob, doc1, :delete)}"
204
+ puts "Can Charlie comment on meeting notes? #{DocumentService.can_user_perform?(charlie, doc2, :comment)}"
205
+ puts "Can Charlie edit project proposal? #{DocumentService.can_user_perform?(charlie, doc3, :edit)}"
206
+
207
+ # Advanced analytics
208
+ puts "\n📊 Document Analytics:"
209
+ analytics = doc1.access_analytics
210
+ puts "Financial Report - Active Users: #{analytics[:active_users].size}"
211
+ puts "Total Collaborators: #{analytics[:total_collaborators]}"
212
+ puts "Permission Breakdown:"
213
+ analytics[:permission_breakdown].each do |user_id, perms|
214
+ puts " #{user_id}: #{perms.join(', ')}"
215
+ end
216
+
217
+ # Demonstrate bit encoding efficiency
218
+ puts "\n⚡ Bit Encoding Efficiency:"
219
+ score = Familia::Features::Relationships::ScoreEncoding.encode_score(Time.now, [:read, :write, :edit, :delete])
220
+ decoded = Familia::Features::Relationships::ScoreEncoding.decode_score(score)
221
+ puts "Encoded score: #{score}"
222
+ puts "Decoded permissions: #{decoded[:permission_list].join(', ')}"
223
+ puts "Permission bits: #{decoded[:permissions]} (#{decoded[:permissions].to_s(2).rjust(8, '0')})"
224
+
225
+ # Cleanup
226
+ puts "\n🧹 Cleanup:"
227
+ [alice, bob, charlie].each { |user| user.documents.clear }
228
+ [doc1, doc2, doc3].each { |doc| doc.clear_all_permissions; doc.collaborators.clear }
229
+
230
+ puts "✅ Integration example completed successfully!"
231
+ puts "\nThis demonstrates:"
232
+ puts "• Fine-grained permission management with 8-bit encoding"
233
+ puts "• Role-based access control with business logic"
234
+ puts "• Time-based analytics and audit trails"
235
+ puts "• Efficient Redis storage with sorted sets"
236
+ puts "• Production-ready error handling and validation"
237
+ end