Module: Familia::Features::ExternalIdentifier

Defined in:
lib/familia/features/external_identifier.rb

Overview

Familia::Features::ExternalIdentifier

Defined Under Namespace

Modules: ClassMethods Classes: ExternalIdentifierError, ExternalIdentifierFieldType

Instance Method Summary collapse

Instance Method Details

#derive_external_identifierString?

Derives a deterministic, public-facing external identifier from the object’s internal objid.

This method uses the objid’s high-quality randomness to seed a pseudorandom number generator (PRNG). The PRNG then acts as a complex, deterministic function to produce a new identifier that has no discernible mathematical correlation to the objid. This is a security measure to prevent leaking information (like timestamps from UUIDv7) from the internal identifier to the public one.

The resulting identifier is always deterministic: the same objid will always produce the same extid, which is crucial for lookups.

Returns:

  • (String, nil)

    A prefixed, base36-encoded external identifier, or nil if the objid is not present.

Raises:



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/familia/features/external_identifier.rb', line 177

def derive_external_identifier
  raise ExternalIdentifierError, 'Missing objid field' unless respond_to?(:objid)

  current_objid = objid
  return nil if current_objid.nil? || current_objid.to_s.empty?

  # Validate objid provenance for security guarantees
  validate_objid_provenance!

  # Normalize the objid to a consistent hex representation first.
  normalized_hex = normalize_objid_to_hex(current_objid)

  # Use the objid's randomness to create a deterministic, yet secure,
  # external identifier. We do not use SecureRandom here because the output
  # must be deterministic.
  #
  # The process is as follows:
  # 1. The objid (a high-entropy value) is hashed to create a uniform seed.
  # 2. The seed initializes a standard PRNG (Random.new).
  # 3. The PRNG acts as a deterministic function to generate a sequence of
  #    bytes that appears random, obscuring the original objid.

  # 1. Create a high-quality, uniform seed from the objid's entropy.
  seed = Digest::SHA256.digest(normalized_hex)

  # 2. Initialize a PRNG with the seed. The same seed will always produce
  #    the same sequence of "random" numbers.
  prng = Random.new(seed.unpack1('Q>'))

  # 3. Generate 16 bytes (128 bits) of deterministic output.
  random_bytes = prng.bytes(16)

  # Encode as a base36 string for a compact, URL-safe identifier.
  # 128 bits is approximately 25 characters in base36.
  external_part = random_bytes.unpack1('H*').to_i(16).to_s(36).rjust(25, '0')

  # Get prefix from feature options, default to "ext"
  options = self.class.feature_options(:external_identifier)
  prefix = options[:prefix] || 'ext'

  "#{prefix}_#{external_part}"
end

#destroy!Object



236
237
238
239
240
241
242
# File 'lib/familia/features/external_identifier.rb', line 236

def destroy!
  # Clean up extid mapping when object is destroyed
  current_extid = instance_variable_get(:@extid)
  self.class.extid_lookup.del(current_extid) if current_extid

  super if defined?(super)
end

#external_identifierString

Full-length alias for extid for clarity when needed

Returns:

  • (String)

    The external identifier



224
225
226
# File 'lib/familia/features/external_identifier.rb', line 224

def external_identifier
  extid
end

#external_identifier=(value) ⇒ Object

Full-length alias setter for extid

Parameters:

  • value (String)

    The external identifier to set



232
233
234
# File 'lib/familia/features/external_identifier.rb', line 232

def external_identifier=(value)
  self.extid = value
end