* Send fallback_original_step_id and always send blob_ref on workflow events
khepri publishes ~f fallback copies of screens for audience-targeted
experiments (WFL-626). Workflow events now carry the copy's
fallback_original_step_id and send blob_ref whenever the SDK has one, so
khepri resolves each step against its exact blob. Experiment params are
read from step metadata first, then param_values.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Trim workflow event comments
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Drop legacy blob_ref decoding for queued workflow events
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Revert unrelated Package.resolved change
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Rename experimentParam to experimentValueFromMetadataOrParams
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Parse workflow experiment enrollment from the config item combo_key
The workflows topic serves each workflow item with a combo_key that
encodes the experiment variants the user was bucketed into
(experiment_id=variant_key pairs joined by ;, or default). Parse it
into WorkflowDataResult.enrolledVariants on every read path so steps
can report experiment exposure in a follow-up. Workflows without an
experiment keep enrolledVariants nil.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Tighten combo_key parsing after review
Drop the redundant default sentinel guard (no pair parses out of it
anyway), keep the moved helpers private, pin that = inside a variant
key is kept, and trim the doc comments.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Echo the step's experiment params on workflow step events
Khepri bakes experiment_id and experiment_variant into the steps of the
enrolled variant (khepri #24946, one experiment per screen) and enrolls
the subscriber on the first step_started that echoes them. Read both
from the step's param_values and put them on every step event.
Drops the combo_key parsing from the previous commits: enrollment must
not be derived from what the config endpoint served, only from exposure.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Trim comments to the ones that carry information
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Reword the experiment param comments
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Plainer wording on the experiment param comments
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Address review: plainer param docs and wire coverage for close
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Delete isLastVariantStep from the SDK
Khepri computes it server side and never puts it in the workflow blob,
so the SDK has nothing to read or send.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Report the workflow blob ref on step events
Khepri needs the blob the client rendered to validate the experiment
params it reports, instead of trusting them. Carry the config item's
blob_ref from the workflows topic through to every step event.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Group the experiment fields and name the ref workflowBlobRef
Per the thread: the blob ref only means something as proof of an
experiment claim, so it travels with the experiment instead of riding
on every workflow event. SDK-level naming is workflowBlobRef so it
does not read as a bare config-layer concept; the wire keys stay
experiment_id, experiment_variant and blob_ref.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Drop comments that restate the code
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Require the workflow blob ref alongside the experiment params
A workflow can only be read through a blob, so a rendered step that has
experiment params always has a ref. Making it required means khepri
never receives a claim it cannot validate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Carry the blob ref through the checkpoint presentation path
Checkpoint workflows build their context through WorkflowPreview, which
left the ref nil, so a checkpoint-presented experiment step reported
nothing once the ref became required. Thread it from the resolved
workflow. Injected dashboard previews still pass none.
ResolvedCheckpointWorkflow moves to its own file, unchanged, to keep the
resolver under the file length limit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Register ResolvedCheckpointWorkflow.swift in RevenueCat.xcodeproj
The legacy project enumerates sources by hand, so a file added outside
Xcode is invisible to it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Name the request property workflowBlobRef
Matches the Android rename; wire name stays blob_ref.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* feat(workflows): add workflows_close abandonment event
Fires a workflow-level workflows_close event when the user dismisses a
workflow without completing it (no purchase or successful restore). It does
not replace paywall_close and is not gated by screen_type, so abandonment on
non-paywall steps is captured too. Port of khepri #21505.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix workflow close completion tracking
* Mark workflow completion before purchase close
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(workflows): add WorkflowEvent model and wire format serialization
Port of https://github.com/RevenueCat/purchases-android/pull/3486
Adds WorkflowEvent (stepStarted/stepCompleted) as the iOS domain model
for workflow step lifecycle events. Adds FeatureEventsRequest.WorkflowEvent
as the Khepri-compatible wire format, wires it into the Feature routing
pipeline, and exposes screenType on WorkflowStep for local context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: register new workflow event files in RevenueCat.xcodeproj
Adds WorkflowEvent.swift, FeatureEventsRequest+WorkflowEvent.swift,
WorkflowEventTests.swift, and WorkflowEventsRequestTests.swift to the
committed SPM-generated Xcode project so CI builds pick them up.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(workflows): address review feedback on WorkflowEvent wire format
- Rename `discriminator` to `type` in FeatureEventsRequest.WorkflowEvent (matches backend expectation and CustomPaywallEvent pattern)
- Add `version: Int = 1` to wire format, matching Android and CustomPaywallEvent schema versioning
- Add `experimentId`, `experimentVariant`, `isLastVariantStep` to WorkflowEvent.Data and wire format Properties (parity with Android/Khepri)
- Add `traceId` to WorkflowEvent.Data and wire format Properties (parity with Android)
- Update tests to cover renamed field, version, new experiment fields, and traceId in JSON shape
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(workflows): handle WorkflowEvent in toMap() and remove shadowing test extension
- Add WorkflowEvent case to FeatureEvent.toMap() so eventsListener receives structured data instead of the unknown fallback
- Remove redundant private extension in WorkflowEventTests that shadowed the @_spi(Internal) public creationData/data accessors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(workflows): address review items - locale in wire format, toMap fields, screenType accessor
- Add localeIdentifier to WorkflowEvent.Data (captured at creation, always sent in wire format context)
- Change Context.locale from String? to String; wire format always includes locale
- Add trace_id, experiment_id, experiment_variant, is_last_variant_step to workflowEventMap()
- Add public stepScreenType accessor on WorkflowStep for SPI consumers
- Add tests for locale in wire format and all workflowEventMap() fields
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(workflows): include locale in workflowEventMap() for hybrid SDK consistency
Matches paywallMap(), customerCenterImpressionMap(), and customerCenterAnswerSubmittedMap()
which all include locale. Hybrid SDK consumers now receive locale for workflow events.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>