familia 2.0.0.pre6 → 2.0.0.pre8

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 (96) 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 -13
  7. data/Gemfile +2 -2
  8. data/Gemfile.lock +3 -3
  9. data/README.md +35 -0
  10. data/docs/wiki/Feature-System-Guide.md +36 -20
  11. data/docs/wiki/Home.md +30 -20
  12. data/docs/wiki/Relationships-Guide.md +684 -0
  13. data/examples/bit_encoding_integration.rb +237 -0
  14. data/examples/redis_command_validation_example.rb +231 -0
  15. data/examples/relationships_basic.rb +273 -0
  16. data/lib/familia/connection.rb +3 -3
  17. data/lib/familia/data_type.rb +7 -4
  18. data/lib/familia/features/encrypted_fields/concealed_string.rb +21 -23
  19. data/lib/familia/features/encrypted_fields.rb +413 -4
  20. data/lib/familia/features/expiration.rb +319 -33
  21. data/lib/familia/features/external_identifiers/external_identifier_field_type.rb +120 -0
  22. data/lib/familia/features/external_identifiers.rb +111 -0
  23. data/lib/familia/features/object_identifiers/object_identifier_field_type.rb +91 -0
  24. data/lib/familia/features/object_identifiers.rb +194 -0
  25. data/lib/familia/features/quantization.rb +385 -44
  26. data/lib/familia/features/relationships/cascading.rb +437 -0
  27. data/lib/familia/features/relationships/indexing.rb +369 -0
  28. data/lib/familia/features/relationships/membership.rb +502 -0
  29. data/lib/familia/features/relationships/permission_management.rb +264 -0
  30. data/lib/familia/features/relationships/querying.rb +615 -0
  31. data/lib/familia/features/relationships/redis_operations.rb +274 -0
  32. data/lib/familia/features/relationships/score_encoding.rb +440 -0
  33. data/lib/familia/features/relationships/tracking.rb +378 -0
  34. data/lib/familia/features/relationships.rb +466 -0
  35. data/lib/familia/features/transient_fields.rb +190 -10
  36. data/lib/familia/features.rb +18 -14
  37. data/lib/familia/horreum/core/serialization.rb +2 -5
  38. data/lib/familia/horreum/subclass/definition.rb +35 -1
  39. data/lib/familia/validation/command_recorder.rb +336 -0
  40. data/lib/familia/validation/expectations.rb +519 -0
  41. data/lib/familia/validation/test_helpers.rb +443 -0
  42. data/lib/familia/validation/validator.rb +412 -0
  43. data/lib/familia/validation.rb +140 -0
  44. data/lib/familia/version.rb +1 -3
  45. data/try/core/errors_try.rb +1 -1
  46. data/try/edge_cases/hash_symbolization_try.rb +1 -0
  47. data/try/edge_cases/reserved_keywords_try.rb +1 -0
  48. data/try/edge_cases/string_coercion_try.rb +2 -0
  49. data/try/encryption/encryption_core_try.rb +3 -1
  50. data/try/features/{encryption_fields → encrypted_fields}/concealed_string_core_try.rb +3 -0
  51. data/try/features/{encryption_fields → encrypted_fields}/context_isolation_try.rb +1 -0
  52. data/try/features/{encrypted_fields_core_try.rb → encrypted_fields/encrypted_fields_core_try.rb} +1 -1
  53. data/try/features/{encrypted_fields_integration_try.rb → encrypted_fields/encrypted_fields_integration_try.rb} +1 -1
  54. data/try/features/{encrypted_fields_no_cache_security_try.rb → encrypted_fields/encrypted_fields_no_cache_security_try.rb} +1 -1
  55. data/try/features/{encrypted_fields_security_try.rb → encrypted_fields/encrypted_fields_security_try.rb} +1 -1
  56. data/try/features/{expiration_try.rb → expiration/expiration_try.rb} +1 -1
  57. data/try/features/external_identifiers/external_identifiers_try.rb +203 -0
  58. data/try/features/object_identifiers/object_identifiers_integration_try.rb +289 -0
  59. data/try/features/object_identifiers/object_identifiers_try.rb +191 -0
  60. data/try/features/{quantization_try.rb → quantization/quantization_try.rb} +1 -1
  61. data/try/features/relationships/categorical_permissions_try.rb +515 -0
  62. data/try/features/relationships/relationships_edge_cases_try.rb +145 -0
  63. data/try/features/relationships/relationships_performance_minimal_try.rb +132 -0
  64. data/try/features/relationships/relationships_performance_simple_try.rb +155 -0
  65. data/try/features/relationships/relationships_performance_try.rb +420 -0
  66. data/try/features/relationships/relationships_performance_working_try.rb +144 -0
  67. data/try/features/relationships/relationships_try.rb +237 -0
  68. data/try/features/{safe_dump_advanced_try.rb → safe_dump/safe_dump_advanced_try.rb} +1 -1
  69. data/try/features/{safe_dump_try.rb → safe_dump/safe_dump_try.rb} +4 -1
  70. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  71. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  72. data/try/features/{transient_fields_core_try.rb → transient_fields/transient_fields_core_try.rb} +1 -1
  73. data/try/features/{transient_fields_integration_try.rb → transient_fields/transient_fields_integration_try.rb} +1 -1
  74. data/try/helpers/test_helpers.rb +1 -1
  75. data/try/horreum/base_try.rb +14 -8
  76. data/try/horreum/enhanced_conflict_handling_try.rb +2 -0
  77. data/try/horreum/relations_try.rb +1 -1
  78. data/try/validation/atomic_operations_try.rb.disabled +320 -0
  79. data/try/validation/command_validation_try.rb.disabled +207 -0
  80. data/try/validation/performance_validation_try.rb.disabled +324 -0
  81. data/try/validation/real_world_scenarios_try.rb.disabled +390 -0
  82. metadata +62 -27
  83. data/docs/wiki/RelatableObjects-Guide.md +0 -563
  84. data/lib/familia/features/relatable_objects.rb +0 -125
  85. data/try/features/relatable_objects_try.rb +0 -220
  86. /data/try/features/{encryption_fields → encrypted_fields}/aad_protection_try.rb +0 -0
  87. /data/try/features/{encryption_fields → encrypted_fields}/error_conditions_try.rb +0 -0
  88. /data/try/features/{encryption_fields → encrypted_fields}/fresh_key_derivation_try.rb +0 -0
  89. /data/try/features/{encryption_fields → encrypted_fields}/fresh_key_try.rb +0 -0
  90. /data/try/features/{encryption_fields → encrypted_fields}/key_rotation_try.rb +0 -0
  91. /data/try/features/{encryption_fields → encrypted_fields}/memory_security_try.rb +0 -0
  92. /data/try/features/{encryption_fields → encrypted_fields}/missing_current_key_version_try.rb +0 -0
  93. /data/try/features/{encryption_fields → encrypted_fields}/nonce_uniqueness_try.rb +0 -0
  94. /data/try/features/{encryption_fields → encrypted_fields}/secure_by_default_behavior_try.rb +0 -0
  95. /data/try/features/{encryption_fields → encrypted_fields}/thread_safety_try.rb +0 -0
  96. /data/try/features/{encryption_fields → encrypted_fields}/universal_serialization_safety_try.rb +0 -0
@@ -0,0 +1,440 @@
1
+ # lib/familia/features/relationships/score_encoding.rb
2
+
3
+ module Familia
4
+ module Features
5
+ module Relationships
6
+ # Score encoding using bit flags for permissions
7
+ #
8
+ # Encodes permissions as bit flags in the decimal portion of Redis sorted set scores:
9
+ # - Integer part: Unix timestamp for time-based ordering
10
+ # - Decimal part: 8-bit permission flags (0-255)
11
+ #
12
+ # Format: [timestamp].[permission_bits]
13
+ # Example: 1704067200.037 = Jan 1, 2024 with read(1) + write(4) + delete(32) = 37
14
+ #
15
+ # Bit positions:
16
+ # 0: read - View/list items
17
+ # 1: append - Add new items
18
+ # 2: write - Modify existing items
19
+ # 3: edit - Edit metadata
20
+ # 4: configure - Change settings
21
+ # 5: delete - Remove items
22
+ # 6: transfer - Change ownership
23
+ # 7: admin - Full control
24
+ #
25
+ # This allows combining permissions (read + delete without write) and efficient
26
+ # permission checking using bitwise operations while maintaining time-based ordering.
27
+ module ScoreEncoding
28
+ # Maximum value for metadata to preserve precision (3 decimal places)
29
+ # For 8-bit permission system, max value is 255
30
+ MAX_METADATA = 255
31
+ METADATA_PRECISION = 1000.0
32
+
33
+ # Permission bit flags (8-bit system)
34
+ PERMISSION_FLAGS = {
35
+ none: 0b00000000, # 0 - No permissions
36
+ read: 0b00000001, # 1 - View/list
37
+ append: 0b00000010, # 2 - Add new items
38
+ write: 0b00000100, # 4 - Modify existing
39
+ edit: 0b00001000, # 8 - Edit metadata
40
+ configure: 0b00010000, # 16 - Change settings
41
+ delete: 0b00100000, # 32 - Remove items
42
+ transfer: 0b01000000, # 64 - Change ownership
43
+ admin: 0b10000000, # 128 - Full control
44
+ }.freeze
45
+
46
+ # Predefined permission combinations
47
+ PERMISSION_ROLES = {
48
+ viewer: PERMISSION_FLAGS[:read],
49
+ editor: PERMISSION_FLAGS[:read] | PERMISSION_FLAGS[:write] | PERMISSION_FLAGS[:edit],
50
+ moderator: PERMISSION_FLAGS[:read] | PERMISSION_FLAGS[:write] | PERMISSION_FLAGS[:edit] | PERMISSION_FLAGS[:delete],
51
+ admin: 0b11111111 # All permissions
52
+ }.freeze
53
+
54
+ # Categorical masks for efficient broad queries
55
+ PERMISSION_CATEGORIES = {
56
+ readable: 0b00000001, # Has basic access
57
+ content_editor: 0b00001110, # Can modify content (append|write|edit)
58
+ administrator: 0b11110000, # Has any admin powers
59
+ privileged: 0b11111110, # Has beyond read-only
60
+ owner: 0b11111111 # All permissions
61
+ }.freeze
62
+
63
+ class << self
64
+ # Get permission bit flag value for a permission symbol
65
+ #
66
+ # @param permission [Symbol] Permission symbol to get value for
67
+ # @return [Integer] Bit flag value for the permission
68
+ # @raise [ArgumentError] If permission is unknown
69
+ def permission_level_value(permission)
70
+ PERMISSION_FLAGS[permission] || raise(ArgumentError, "Unknown permission: #{permission.inspect}")
71
+ end
72
+
73
+ # Encode timestamp and permission (alias for encode_score)
74
+ #
75
+ # @param timestamp [Time, Integer] The timestamp to encode
76
+ # @param permission [Symbol, Integer, Array] Permission(s) to encode
77
+ # @return [Float] Encoded score suitable for Redis sorted sets
78
+ def permission_encode(timestamp, permission)
79
+ encode_score(timestamp, permission)
80
+ end
81
+
82
+ # Decode score into permission information
83
+ #
84
+ # @param score [Float] The encoded score
85
+ # @return [Hash] Hash with timestamp, permissions bits, and permission list
86
+ def permission_decode(score)
87
+ decoded = decode_score(score)
88
+ {
89
+ timestamp: decoded[:timestamp],
90
+ permissions: decoded[:permissions],
91
+ permission_list: decoded[:permission_list]
92
+ }
93
+ end
94
+
95
+ # Encode a timestamp and permissions into a Redis score
96
+ #
97
+ # @param timestamp [Time, Integer] The timestamp to encode
98
+ # @param permissions [Integer, Symbol, Array] Permissions to encode
99
+ # @return [Float] Encoded score suitable for Redis sorted sets
100
+ #
101
+ # @example Basic encoding with bit flag
102
+ # encode_score(Time.now, 5) # read(1) + write(4) = 5
103
+ # #=> 1704067200.005
104
+ #
105
+ # @example Permission symbol encoding
106
+ # encode_score(Time.now, :read)
107
+ # #=> 1704067200.001
108
+ #
109
+ # @example Multiple permissions
110
+ # encode_score(Time.now, [:read, :write, :delete])
111
+ # #=> 1704067200.037
112
+ def encode_score(timestamp, permissions = 0)
113
+ time_part = timestamp.respond_to?(:to_i) ? timestamp.to_i : timestamp
114
+
115
+ permission_bits = case permissions
116
+ when Symbol
117
+ PERMISSION_ROLES[permissions] || PERMISSION_FLAGS[permissions] || 0
118
+ when Array
119
+ # Support array of permission symbols
120
+ permissions.reduce(0) { |acc, p| acc | (PERMISSION_FLAGS[p] || 0) }
121
+ when Integer
122
+ validate_permission_bits(permissions)
123
+ else
124
+ 0
125
+ end
126
+
127
+ time_part + (permission_bits / METADATA_PRECISION)
128
+ end
129
+
130
+ # Decode a Redis score back into timestamp and permissions
131
+ #
132
+ # @param score [Float] The encoded score
133
+ # @return [Hash] Hash with :timestamp, :permissions, and :permission_list keys
134
+ #
135
+ # @example Basic decoding
136
+ # decode_score(1704067200.037)
137
+ # #=> { timestamp: 1704067200, permissions: 37, permission_list: [:read, :write, :delete] }
138
+ def decode_score(score)
139
+ return { timestamp: 0, permissions: 0, permission_list: [] } unless score.is_a?(Numeric)
140
+
141
+ time_part = score.to_i
142
+ permission_bits = ((score - time_part) * METADATA_PRECISION).round
143
+
144
+ {
145
+ timestamp: time_part,
146
+ permissions: permission_bits,
147
+ permission_list: decode_permission_flags(permission_bits)
148
+ }
149
+ end
150
+
151
+ # Check if score has specific permissions
152
+ #
153
+ # @param score [Float] The encoded score
154
+ # @param permissions [Array<Symbol>] Permissions to check
155
+ # @return [Boolean] True if all permissions are present
156
+ #
157
+ # @example
158
+ # permission?(1704067200.005, :read) # score has read(1) + write(4)
159
+ # #=> true
160
+ def permission?(score, *permissions)
161
+ decoded = decode_score(score)
162
+ permission_bits = decoded[:permissions]
163
+
164
+ permissions.all? do |perm|
165
+ flag = PERMISSION_FLAGS[perm]
166
+ flag && permission_bits.anybits?(flag)
167
+ end
168
+ end
169
+
170
+ # Add permissions to existing score
171
+ #
172
+ # @param score [Float] The existing encoded score
173
+ # @param permissions [Array<Symbol>] Permissions to add
174
+ # @return [Float] New score with added permissions
175
+ #
176
+ # @example
177
+ # add_permissions(1704067200.001, :write, :delete) # add write(4) + delete(32) to read(1)
178
+ # #=> 1704067200.037
179
+ def add_permissions(score, *permissions)
180
+ decoded = decode_score(score)
181
+ current_bits = decoded[:permissions]
182
+
183
+ new_bits = permissions.reduce(current_bits) do |acc, perm|
184
+ acc | (PERMISSION_FLAGS[perm] || 0)
185
+ end
186
+
187
+ encode_score(decoded[:timestamp], new_bits)
188
+ end
189
+
190
+ # Remove permissions from existing score
191
+ #
192
+ # @param score [Float] The existing encoded score
193
+ # @param permissions [Array<Symbol>] Permissions to remove
194
+ # @return [Float] New score with removed permissions
195
+ #
196
+ # @example
197
+ # remove_permissions(1704067200.037, :write) # remove write(4) from read(1)+write(4)+delete(32)
198
+ # #=> 1704067200.033
199
+ def remove_permissions(score, *permissions)
200
+ decoded = decode_score(score)
201
+ current_bits = decoded[:permissions]
202
+
203
+ new_bits = permissions.reduce(current_bits) do |acc, perm|
204
+ acc & ~(PERMISSION_FLAGS[perm] || 0)
205
+ end
206
+
207
+ encode_score(decoded[:timestamp], new_bits)
208
+ end
209
+
210
+ # Create score range for permissions
211
+ #
212
+ # @param min_permissions [Array<Symbol>, nil] Minimum required permissions
213
+ # @param max_permissions [Array<Symbol>, nil] Maximum allowed permissions
214
+ # @return [Array<Float>] Min and max scores for Redis range queries
215
+ #
216
+ # @example
217
+ # permission_range([:read], [:read, :write])
218
+ # #=> [0.001, 0.005]
219
+ def permission_range(min_permissions = [], max_permissions = nil)
220
+ min_bits = Array(min_permissions).reduce(0) { |acc, p| acc | (PERMISSION_FLAGS[p] || 0) }
221
+ max_bits = if max_permissions
222
+ Array(max_permissions).reduce(0) do |acc, p|
223
+ acc | (PERMISSION_FLAGS[p] || 0)
224
+ end
225
+ else
226
+ 255
227
+ end
228
+
229
+ [min_bits / METADATA_PRECISION, max_bits / METADATA_PRECISION]
230
+ end
231
+
232
+ # Get current timestamp as score (no permissions)
233
+ #
234
+ # @return [Float] Current time as Redis score
235
+ def current_score
236
+ encode_score(Time.now, 0)
237
+ end
238
+
239
+ # Create score range for Redis operations based on time bounds
240
+ #
241
+ # @param start_time [Time, nil] Start time (nil for -inf)
242
+ # @param end_time [Time, nil] End time (nil for +inf)
243
+ # @param min_permissions [Array<Symbol>, nil] Minimum required permissions
244
+ # @return [Array] Array suitable for Redis ZRANGEBYSCORE operations
245
+ #
246
+ # @example Time range
247
+ # score_range(1.hour.ago, Time.now)
248
+ # #=> [1704063600.0, 1704067200.255]
249
+ #
250
+ # @example Permission filter
251
+ # score_range(nil, nil, min_permissions: [:read])
252
+ # #=> [0.001, "+inf"]
253
+ def score_range(start_time = nil, end_time = nil, min_permissions: nil)
254
+ min_bits = if min_permissions
255
+ Array(min_permissions).reduce(0) do |acc, p|
256
+ acc | (PERMISSION_FLAGS[p] || 0)
257
+ end
258
+ else
259
+ 0
260
+ end
261
+
262
+ min_score = if start_time
263
+ encode_score(start_time, min_bits)
264
+ elsif min_permissions
265
+ encode_score(0, min_bits)
266
+ else
267
+ '-inf'
268
+ end
269
+
270
+ max_score = if end_time
271
+ encode_score(end_time, 255) # Use max valid permission bits
272
+ else
273
+ '+inf'
274
+ end
275
+
276
+ [min_score, max_score]
277
+ end
278
+
279
+ # Decode permission bits into array of permission symbols
280
+ #
281
+ # @param bits [Integer] Permission bits to decode
282
+ # @return [Array<Symbol>] Array of permission symbols
283
+ def decode_permission_flags(bits)
284
+ PERMISSION_FLAGS.select { |_name, flag| bits.anybits?(flag) }.keys
285
+ end
286
+
287
+ # Check broad permission categories
288
+ #
289
+ # @param score [Float] The encoded score
290
+ # @param category [Symbol] Category to check (:readable, :content_editor, :administrator, etc.)
291
+ # @return [Boolean] True if score meets the category requirements
292
+ def category?(score, category)
293
+ decoded = decode_score(score)
294
+ permission_bits = decoded[:permissions]
295
+
296
+ mask = PERMISSION_CATEGORIES[category]
297
+ return false unless mask
298
+
299
+ permission_bits.anybits?(mask)
300
+ end
301
+
302
+ # Filter collection by permission category
303
+ #
304
+ # @param scores [Array<Float>] Array of scores to filter
305
+ # @param category [Symbol] Category to filter by
306
+ # @return [Array<Float>] Scores matching the category
307
+ def filter_by_category(scores, category)
308
+ mask = PERMISSION_CATEGORIES[category]
309
+ return [] unless mask
310
+
311
+ scores.select do |score|
312
+ permission_bits = ((score % 1) * METADATA_PRECISION).round
313
+ permission_bits.anybits?(mask)
314
+ end
315
+ end
316
+
317
+ # Get permission tier for score
318
+ #
319
+ # @param score [Float] The encoded score
320
+ # @return [Symbol] Permission tier (:administrator, :content_editor, :viewer, :none)
321
+ def permission_tier(score)
322
+ decoded = decode_score(score)
323
+ bits = decoded[:permissions]
324
+
325
+ if bits.anybits?(PERMISSION_CATEGORIES[:administrator])
326
+ :administrator
327
+ elsif bits.anybits?(PERMISSION_CATEGORIES[:content_editor])
328
+ :content_editor
329
+ elsif bits.anybits?(PERMISSION_CATEGORIES[:readable])
330
+ :viewer
331
+ else
332
+ :none
333
+ end
334
+ end
335
+
336
+ # Efficient bulk categorization
337
+ #
338
+ # @param scores [Array<Float>] Array of scores to categorize
339
+ # @return [Hash] Hash mapping tiers to arrays of scores
340
+ def categorize_scores(scores)
341
+ scores.group_by { |score| permission_tier(score) }
342
+ end
343
+
344
+ # Check if permissions meet minimum category
345
+ #
346
+ # @param permission_bits [Integer] Permission bits to check
347
+ # @param category [Symbol] Category to check against
348
+ # @return [Boolean] True if permissions meet the category requirements
349
+ def meets_category?(permission_bits, category)
350
+ mask = PERMISSION_CATEGORIES[category]
351
+ return false unless mask
352
+
353
+ case category
354
+ when :readable
355
+ permission_bits.positive? # Any permission implies read
356
+ when :privileged
357
+ permission_bits > 1 # More than just read
358
+ when :administrator
359
+ permission_bits.anybits?(PERMISSION_CATEGORIES[:administrator])
360
+ else
361
+ permission_bits.anybits?(mask)
362
+ end
363
+ end
364
+
365
+ # Range queries for categorical filtering
366
+ #
367
+ # @param category [Symbol] Category to create range for
368
+ # @param start_time [Time, nil] Optional start time filter
369
+ # @param end_time [Time, nil] Optional end time filter
370
+ # @return [Array<String>] Min and max range strings for Redis queries
371
+ def category_score_range(category, start_time = nil, end_time = nil)
372
+ PERMISSION_CATEGORIES[category] || 0
373
+
374
+ # Any permission matching the category mask
375
+ min_score = start_time ? start_time.to_i : 0
376
+ max_score = end_time ? end_time.to_i : Time.now.to_i
377
+
378
+ # Return range that includes any matching permissions
379
+ ["#{min_score}.000", "#{max_score}.999"]
380
+ end
381
+
382
+ private
383
+
384
+ # Validate permission bits are within acceptable range
385
+ #
386
+ # @param bits [Integer] Permission bits to validate
387
+ # @return [Integer] Validated permission bits
388
+ # @raise [ArgumentError] If bits are outside 0-255 range
389
+ def validate_permission_bits(bits)
390
+ raise ArgumentError, 'Permission bits must be 0-255' unless (0..255).cover?(bits)
391
+
392
+ bits
393
+ end
394
+ end
395
+
396
+ # Instance methods for classes that include this module
397
+ def encode_score(timestamp, permissions = 0)
398
+ ScoreEncoding.encode_score(timestamp, permissions)
399
+ end
400
+
401
+ def decode_score(score)
402
+ ScoreEncoding.decode_score(score)
403
+ end
404
+
405
+ def permission?(score, *permissions)
406
+ ScoreEncoding.permission?(score, *permissions)
407
+ end
408
+
409
+ def add_permissions(score, *permissions)
410
+ ScoreEncoding.add_permissions(score, *permissions)
411
+ end
412
+
413
+ def remove_permissions(score, *permissions)
414
+ ScoreEncoding.remove_permissions(score, *permissions)
415
+ end
416
+
417
+ def permission_range(min_permissions = [], max_permissions = nil)
418
+ ScoreEncoding.permission_range(min_permissions, max_permissions)
419
+ end
420
+
421
+ def current_score
422
+ ScoreEncoding.current_score
423
+ end
424
+
425
+ def score_range(start_time = nil, end_time = nil, min_permissions: nil)
426
+ ScoreEncoding.score_range(start_time, end_time, min_permissions: min_permissions)
427
+ end
428
+
429
+ # Legacy method aliases for backward compatibility
430
+ def permission_encode(timestamp, permission)
431
+ ScoreEncoding.permission_encode(timestamp, permission)
432
+ end
433
+
434
+ def permission_decode(score)
435
+ ScoreEncoding.permission_decode(score)
436
+ end
437
+ end
438
+ end
439
+ end
440
+ end