Return Types & Sentinels

Frozen BucketedVariation/BucketedFeature, the sentinel contract, enums

This page documents the value objects the decision methods return, the enums you pass in, and — most importantly — the sentinel return contract: how the SDK signals a business miss by value instead of by exception. The decision methods never raise and never return a bare nil for a business miss.

All types live under the ConvertSdk module.

The sentinel return contract

Decisioning methods return a frozen value object on every call:

  • A hit returns a frozen BucketedVariation (or BucketedFeature): #key is the real key, #error? is false.
  • A miss returns a frozen Sentinel: #key is always nil, #error? is always true, and #to_s is the wire string.

This is architecture Decision 2: misses are signaled by value, never by exception. It is why a single case branch handles both outcomes:

case (variation = context.run_experience("homepage-test")).key
when nil then render_default            # Sentinel — a business miss
else          render_variation(variation.key)
end

Sentinel implements exactly three methods:

MethodReturns
#to_sThe byte-identical JS wire string (appears in payloads/logs).
#keyAlways nil — so case variation&.key falls through to the nil branch.
#error?Always true — the value-object discriminator distinguishing a sentinel from a real decision.

Sentinels are exposed as frozen singleton constants, so you can branch on object identity for granular handling:

result = context.run_experience("homepage-test")
case result.key
when nil
  show_default if result.equal?(ConvertSdk::RuleError::NO_DATA_FOUND)
else
  render_variation(result.key)
end

Equality is left as default object identity (no == override). Two distinct Sentinel instances built from the same wire string are not equal — which is why the canonical instances live as shared frozen constants.

The sentinel constants

ConstantWire string
ConvertSdk::RuleError::NO_DATA_FOUNDconvert.com_no_data_found
ConvertSdk::RuleError::NEED_MORE_DATAconvert.com_need_more_data
ConvertSdk::BucketingError::VARIATION_NOT_DECIDEDconvert.com_variation_not_decided

RuleError sentinels signal rule/audience-evaluation misses; BucketingError::VARIATION_NOT_DECIDED signals that no variation could be bucketed. The wire strings are byte-identical to the JS SDK.

Never-raises guarantee

run_experience returns RuleError::NO_DATA_FOUND on an internal failure (plus an error log line); run_experiences and run_features degrade to []; run_feature degrades to a DISABLED BucketedFeature. A raising collaborator degrades the call — it never crashes your host request (NFR9).

BucketedVariation

Returned by run_experience (hit) and as the elements of run_experiences. A frozen Struct with snake_case members, aligned to the JS BucketedVariation shape:

ConvertSdk::BucketedVariation.new(
  experience_id:,
  experience_key:,
  experience_name:,
  bucketing_allocation:,
  id:,
  key:,
  name:,
  status:,
  traffic_allocation:,
  changes:
)
  • key — the variation key from the Convert dashboard; this is what you branch on.
  • id / experience_id — the variation and experience identifiers (used on the bucketing wire event).
  • #error? — always false (a real decision is never a business miss).
variation = context.run_experience("homepage-test")
case variation&.key
when nil          then render_default     # sentinel miss
when "treatment"  then render_treatment
else                   render_variation(variation.key)
end

BucketedFeature and FeatureStatus

Returned by run_feature (a single feature, or an Array when several bucketed variations carry the feature) and as the elements of run_features. A frozen Struct with snake_case members:

ConvertSdk::BucketedFeature.new(
  experience_id:,
  experience_key:,
  experience_name:,
  id:,
  key:,
  name:,
  status:,    # a FeatureStatus wire value
  variables:  # the feature's variable map
)
  • #error? — always false.

A feature miss is not a sentinel — it is a frozen DISABLED BucketedFeature. Branch on #status:

feature = context.run_feature("new-checkout")
if feature.status == ConvertSdk::FeatureStatus::ENABLED
  render_new_checkout(feature.variables["headline"])
else
  render_legacy_checkout
end

FeatureStatus is a module of wire-string constants (byte-identical to the JS SDK):

ConstantWire string
ConvertSdk::FeatureStatus::ENABLEDenabled
ConvertSdk::FeatureStatus::DISABLEDdisabled

GoalDataKey

The eight platform keys you may attach to a conversion via track_conversion(..., goal_data: { ... }). The public Ruby surface accepts snake_case symbol keys; the SDK translates them to the camelCase wire identifier (the camelCase wire form is also accepted for integrators who already know the platform identifiers). Any key outside these eight is rejected and debug-logged.

Public snake_case keyGoalDataKey constantWire string
amountAMOUNTamount
products_countPRODUCTS_COUNTproductsCount
transaction_idTRANSACTION_IDtransactionId
custom_dimension_1CUSTOM_DIMENSION_1customDimension1
custom_dimension_2CUSTOM_DIMENSION_2customDimension2
custom_dimension_3CUSTOM_DIMENSION_3customDimension3
custom_dimension_4CUSTOM_DIMENSION_4customDimension4
custom_dimension_5CUSTOM_DIMENSION_5customDimension5
context.track_conversion(
  "purchase",
  goal_data: { amount: 49.99, products_count: 3, transaction_id: "tx-42" }
)

See Tracking Conversions.

LogLevel

Integer thresholds (JS-parity) passed as log_level: to ConvertSdk.create. A message emits when its level is >= the configured threshold.

ConstantValue
ConvertSdk::LogLevel::TRACE0
ConvertSdk::LogLevel::DEBUG1
ConvertSdk::LogLevel::INFO2
ConvertSdk::LogLevel::WARN3
ConvertSdk::LogLevel::ERROR4
ConvertSdk::LogLevel::SILENT5

The default is DEBUG. See Configuration Options.

SystemEvents

Lifecycle event names you can subscribe to with Client#on(event, &block). Each is a String constant on ConvertSdk::SystemEvents (byte-identical to the JS SDK); the matching string works too.

ConstantEvent name
READYready
CONFIG_UPDATEDconfig.updated
BUCKETINGbucketing
CONVERSIONconversion
SEGMENTSsegments
API_QUEUE_RELEASEDapi.queue.released
LOCATION_ACTIVATEDlocation.activated
LOCATION_DEACTIVATEDlocation.deactivated
AUDIENCESaudiences
DATASTORE_QUEUE_RELEASEDdatastore.queue.released

See Event System.

Next steps