29 Commits
Author SHA1 Message Date
Facundo MenzellaandClaude Opus 5.5 8205ba615f other: move the checkpoint samples to SampleCat and delete CheckpointTester (#8162)
* other(samplecat): add a Checkpoints tab with 4 samples

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* other: delete CheckpointTester

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* other: point agents to SampleCat for integration examples

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* other(samplecat): use result.hasEntitlement in checkpoint callbacks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* other(ci): add a SampleCat smoke build with Tuist (#8163)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 10:34:19 +00:00
Rosie Watson 40447f4551 WFL-800 send step analytics for all step types in a flow (#8160)
* chore: track non-ui flow steps

* refactor: clean up code style

* chore: update project

* chore: fix up comments

* fix: make sure a failure uses the correct trace ID

* chore: remove reference to a single step flow

* refactor: clean up array threading

* refactor: inline function

* chore: remove unused offerings
2026-11-02 08:24:26 +00:00
Peter Porfy 7da3bd8d9c feat(checkpoints): ad checkpoint presenter (#7783)
* feat(checkpoints): ad presenter layer

Resolves checkpoint workflows whose only step is an ad step to a
registered AdPresenter, adapted to the CheckpointResult API: a shown or
rewarded ad passes with the latest customer info, a failed ad or a
missing presenter passes with no flow.

* refactor(checkpoints): fold the ad outcome into the checkpoint result

* refactor(checkpoints): fail the checkpoint when an ad could not be shown

* refactor: moved to file private and added explanation to null customerinfo

* fix(checkpoints): suppress the checkpoint when nothing was presented
2026-10-29 15:15:07 +01:00
Cesar de la Vega f38cd5c475 Parse paywall gating in checkpoint workflows (#8121)
* Parse paywall gating in checkpoint workflows

* Test invalid gating on terminal offering workflows
2026-10-26 16:01:46 +01:00
Facundo MenzellaandClaude Opus 5.5 3d03673f61 other: attribute checkpoint offering purchases to their hit (#8058)
* Attribute checkpoint offering purchases to the hit trace id

Put the hit's trace id on the matched offering's presented context and send it as presented_trace_id on receipts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Require traceID in withCheckpointIdentifier

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Use the checkpoint trace id for the offering's workflow

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Carry the hit trace id on matchedOffering

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Require traceID in withPresentedCheckpointIdentifier and tighten trace id tests

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Create the matched offering and its trace id in one place

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 16:53:46 +02:00
Cesar de la VegaandRevenueCat Git Bot c516c1df39 Attribute workflow purchases to their launching checkpoint (#8037)
* feat(checkpoints): attribute purchases to launching checkpoint

* fix test

* Update baseline swiftinterface files for `cesar/wfl-734-ios-attribute-workflow-purchases-to-their-launching` (#8042)

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2026-10-16 15:37:29 +00:00
JayShortway d93dd7a585 Rename simulated error checkpoint identifier (#8047) 2026-10-16 17:21:34 +02:00
Rick e8a236c982 fix(checkpoints): lower unknown checkpoint log level (#7970) 2026-10-12 14:21:20 +02:00
Facundo MenzellaandClaude Opus 5.5 06cd8c171c other: Add trace_id to the checkpoint hit event (#7811)
* Add trace_id to the checkpoint hit event

The id is created when the checkpoint is evaluated and handed to the workflow it
starts, so the hit, the workflow's step and paywall events and the receipt's
paywall trace_id all share it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Drop Android references from trace id comments

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Remove remaining Android references in workflow event comments

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 10:43:35 +02:00
Rick 102272d6e9 feat(checkpoints): follow-up improvements (#7752)
* fix: require workflow step types

* fix: resolve offerings from workflow steps

* chore: improve local rule evaluation logs

* feat: align custom variable validation with Android

* docs: clarify checkpoint callback execution

* Fix required workflow step type tests

* fix: avoid logging cancelled rule evaluations

* fix: scope workflow offering fallbacks
2026-09-18 13:10:02 +02:00
Rick a3dc5f14cc feat(checkpoints): add app-owned offering presenter API (#7626)
* feat(checkpoints): add app-owned offering presenter API

* refactor(checkpoints): rename offering presenter to paywall presenter

* Add paywall presenter API for checkpoint offerings

* Fix paywall presenter linting

* Map paywall presenter outcomes to checkpoint results

* Add local checkpoint paywall presenter overrides

* Add checkpoint paywall presenter demo to tester

* Fix paywall presenter integration after rebase

* Pass purchase results through custom paywall presenters

* Add checkpoint presenter files to Xcode project

* Align checkpoint tester with FlowResult API

* Remove legacy checkpoint result APIs

* Synchronize customer info after custom paywall presentation

* Refine checkpoint paywall presentation handling

* Merge checkpoint workflow execution into presenter

* Centralize checkpoint paywall presenter selection

* Fix checkpoint tester paywall presentation lifecycle

* Release checkpoint presentation before syncing

* Rename global checkpoint paywall presenter

* Internalize paywall presentation params initializer

* Capture paywall presenter before checkpoint resolution

* Make paywall presentation result forward compatible

* Cover stale checkpoint presentation completions

* Extract checkpoint presentation slot

* Unify checkpoint presentation coordination

* Simplify checkpoint manager dependencies

* Simplify checkpoint presentation outcomes

* Internalize checkpoint workflow state

* Clarify checkpoint presentation naming

* Align presenter API with callback-only checkpoints

* Simplify checkpoint presentation internals

* Handle terminal offering steps in paywall resolution

* Disable exit offers for checkpoint paywalls

* Clarify obtained entitlement semantics

* Complete cancellation for detached checkpoint paywalls

* Update checkpoint files in Xcode project

* Avoid Swift if expression in checkpoint presenter

* Guard workflow presenter execution tests by platform

* Support older Swift checkpoint presenter builds

* Clarify checkpoint presentation type naming

* Clarify continued paywall result documentation

* Preserve completed default paywall outcomes

* Move checkpoint exit-offer opt-out to follow-up

* Remove cancellation handling for the retired async checkpoint API
2026-09-18 10:01:36 +02:00
Facundo MenzellaandClaude Opus 5 bf4f81c287 other: report what a checkpoint resolved to on the hit event (#7729)
* feat: report what a checkpoint resolved to on the hit event

The hit only carried the identifier, so nothing downstream could tell a
checkpoint that presented a paywall from one that matched nothing. It now
carries checkpoint_type, result, and the workflow, offering and rule ids.

The hit moves to after resolution so it has an outcome to report. A resolution
that throws now reports nothing, which is the one case where registering the
identifier is lost.

Picks up cesar's #7563 while he is out. Outcome fields are optional so hits
stored by an older SDK version still decode off disk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAMocQ6tct3P2rexywxjLu

* refactor: require the checkpoint outcome and trim comments

Checkpoint hits have not shipped, so there are no stored events with the old
shape and checkpoint_type and result do not need to be optional. The three ids
stay optional because they only exist when the checkpoint resolved to them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAMocQ6tct3P2rexywxjLu

* fix: date the checkpoint hit before resolving it

Resolution awaits rules, audiences, workflow data and offerings, so taking the
date afterwards reported resolution-completion time as the hit time. On a cold
start that drifts by a full network round trip.

Adds coverage for the resolution to event mapping, which nothing exercised:
present_ui with its workflow id, return_data with its offering id, and each
no-action reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAMocQ6tct3P2rexywxjLu

* chore: register the new test file in RevenueCat.xcodeproj

The CI test plans point at container:RevenueCat.xcodeproj, which lists test
files explicitly, so without this the new tests never run on CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FAMocQ6tct3P2rexywxjLu

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 16:14:44 +02:00
Rick 80a6922626 refactor(checkpoints): remove checkpoint listener API (#7672)
* Resolve offerings per checkpoint workflow step

* Remove checkpoint listener API

* test: preserve checkpoint result API coverage

* Fix duplicate workflow error handler initializer

* Use workflow offering resolver for checkpoints
2026-09-14 15:30:26 +02:00
Rick 4cb71a6ad5 feat(checkpoints): Resolve the offering per workflow step (#7658)
* Treat unresolved rule variables as no match

* Read checkpoint offering from initial step

* Support untyped checkpoint workflows

* Resolve offerings per checkpoint workflow step

* Refactor workflow presentation failure state

* Unify workflow presentation error logging

* Fix workflow test lint

* Test invalid workflow offering identifiers

* Restore workflow step type fallback

* Restrict workflow error handling to supported platforms

* Report unavailable initial workflow screens

* fix(checkpoints): preserve outcomes on workflow errors

* refactor: model workflow presentation failures explicitly

* refactor: make workflow error callback optional

* fix: preserve back navigation on first workflow transition

* refactor: refine workflow presentation error handling

* Align initial workflow errors with Android

* test: guard UIKit workflow tests by platform

* refactor: split workflow offering resolution

* fix: compile workflow offering resolution
2026-09-14 15:30:26 +02:00
Facundo MenzellaandClaude Opus 5 02b4a0a850 other: echo step experiment params on workflow events (#7659)
* 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>
2026-09-10 15:30:46 +02:00
Rick 709211517c Add checkpoint rule evaluation logging (#7650) 2026-09-08 16:16:01 +02:00
Rick 3216c1b083 refactor(checkpoints): remove backend predicate results (#7603) 2026-09-08 16:16:00 +02:00
Rick e3c4307049 fix(checkpoints): fix audience loading and missing-dimension evaluation (#7602)
* chore: suppress baseline test size lint violations

* Ensure audience topic blobs are ready

* Treat unresolved rule variables as no match

* test(checkpoints): treat unresolved audience variables as no match

* test(remote-config): expect stale ui config rejection

* refactor(logging): split checkpoint and local rules strings

* fix(local-rules): log unresolved rule variables

* test(remote-config): expect consistent ui config retry
2026-09-08 16:15:59 +02:00
Rick 4939ef18e3 fix(checkpoints): ensure remote config reads are generation consistent (#7575)
* fix(checkpoints): ensure remote config reads are generation consistent

* refactor(Checkpoints): pass generation into consistent reads

* test(remote-config): update generation retry expectations

* chore: suppress baseline test size lint violations

* chore: suppress baseline test lint warnings

* Address remote config consistency review feedback

* Fix stale remote config reads for checkpoints

* Retry superseded remote config errors

* Respect cancellation during remote config reads

* Preserve stale workflow configuration errors

* Preserve workflow read cancellation and staleness

* Remove obsolete checkpoint configuration error

* test(remote-config): support older Swift toolchains

* fix(checkpoints): propagate workflow cancellation
2026-09-08 15:46:57 +02:00
Rick 8b024e8009 feat(remote-config): remove the remote config session kill switch (#7524)
* Remove remote config kill switch

# Conflicts:
#	Sources/Checkpoints/CheckpointWorkflowResolver.swift
#	Tests/UnitTests/Checkpoints/CheckpointWorkflowResolverTests.swift

* Improve remote config retry tests

* feat(remote-config): stop decoding offering paywall components

* test(remote-config): align workflow integration test naming

* refactor(remote-config): limit offerings preview APIs to debug builds

* refactor(remote-config): address review cleanup

* fix(remote-config): propagate workflow fetch failures

* refactor(remote-config): derive paywall components from decoding mode

* Remove offerings paywall component decoding

* Update offerings component decoding tests

* Fix CI test fixture compilation

* Fix offerings initializer argument order

* Preserve default offerings component marker
2026-09-08 07:06:04 +02:00
Rick c97d690c90 fix(Checkpoints): Fail rule resolution when the app user changes (#7554)
* fix: fail rule resolution when customer changes

* refactor: use app user terminology

* fix: defer stale audience evaluation errors

* docs: clarify audience evaluation failure

* test(Checkpoints): provide app user ID to rule evaluator
2026-09-07 09:25:32 +02:00
Rick 3415962003 feat(Checkpoints): consume canonical audience configuration (#7543)
* feat(Checkpoints): consume canonical audience configuration

* feat(Checkpoints): support typed backend audience values

* fix(Checkpoints): retry stale checkpoint resolution

* fix(Checkpoints): isolate malformed audience entries

* fix(Checkpoints): align audience identifier handling

* fix(Checkpoints): log unsupported backend audience values

* test(Checkpoints): inline canonical audience fixtures

* feat(Checkpoints): evaluate backend audience predicates

* fix(Checkpoints): bound audience resolution retries

* refactor(Checkpoints): share audience value conversion

* fix(Checkpoints): tighten configuration consistency checks

* test(Checkpoints): format dimension value fixture

* test(Checkpoints): fix null dimension conversion expectation
2026-09-07 07:45:27 +02:00
Rick 967cc710e0 feat(Checkpoints): refine result and listener APIs (#7526)
* feat(Checkpoints): add web checkout outcome

* docs(Checkpoints): align documentation with Android

* refactor(Checkpoints): introduce listener contexts

* refactor(Checkpoints): align resolution behavior with Android

* Refine checkpoint paywall outcome names

* Remove checkpoint model equality conformances
2026-09-01 16:29:11 +02:00
Facundo MenzellaandClaude Opus 5 4b95788014 Evaluate checkpoint audiences 2/2 (#7426)
* Deserialize audience rules for rules engine evaluation

The rules engine takes a predicate as a string, so decoding the audience
as an opaque blob would only mean serializing it back before every
evaluation. Decode `id` and keep `rules` as the JSON string the engine
takes.

Keys are sorted on the way out because Swift dictionaries have no order
of their own, and an unstable predicate string would be a moving target
for anything that caches or compares it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UND779kHH6TRc2662wzaj1

* Evaluate checkpoint audiences

Checkpoint rules each target an audience, but resolution took the first
served rule regardless. Resolve each rule's audience through the
`audiences` topic and evaluate its predicate, so the first rule whose
audience the customer matches wins.

Predicates resolve one rule at a time, so a rule after the match never
pays for a lookup. An audience the SDK couldn't read leaves the outcome
unknown, so it reports `configurationUnavailable` rather than `noMatch`,
and can't let a lower-priority rule win instead.

Also starts honoring `CheckpointParams.customVariables`, which the
resolver dropped until now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UND779kHH6TRc2662wzaj1

* Cover a malformed audience falling through to a later rule

Ports the one case purchases-android#3971 tests that iOS did not: a
predicate the engine can't run is an evaluation failure, not a lookup
failure, so a lower-priority rule can still win on its own merits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UND779kHH6TRc2662wzaj1

* Cover served audience payload shapes and the terminal-offering gate

Decodes through the real Audience decoder so the predicate under test is
what a served payload produces: empty rules match everyone (the engine
only dispatches single-key objects, and every object is truthy), the
backend's always-false sentinel resolves to a clean non-match, and a
predicate on a variable no provider supplies is false rather than an
error.

Also covers that the audience gate runs before the workflow body picks
between presenting and handing back an offering.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UND779kHH6TRc2662wzaj1

* Read audiences from the topic item instead of a blob

The audiences topic serves each audience inline in its config item, with
no blob_ref, so the blob read found nothing and every lookup returned
nil. With audience evaluation in place that surfaced as
configurationUnavailable for every checkpoint, so nothing presented.

Read the item's own content first and keep the blob read as a fallback
for any project still served the earlier shape.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UND779kHH6TRc2662wzaj1

* Pin that a negated unsupplied variable matches everyone

An unresolvable path does not fail closed. Under negation it inverts:
null == "NL" is false, so ! makes it true and the audience matches every
customer, including the ones it was written to exclude.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UND779kHH6TRc2662wzaj1

* Read audiences from item metadata only, matching Android

khepri publishes each audience as item metadata with blob_contents=None
and clear_blob=True, so the blob path is deliberately dead rather than
transitional. Drop the fallback and log the raw metadata before decoding,
matching purchases-android#3971.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UND779kHH6TRc2662wzaj1

* Log audience metadata as JSON instead of parsed values

The interpolation happened at the call site, so the dictionary was
stringified even with debug logging off, and it printed AnyDecodable
case names rather than anything a human wants to read. Reuse the JSON
already built for decoding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UND779kHH6TRc2662wzaj1

* Check config staleness before resolving the matched rule

Audiences are read from their own topic at lookup time, so they are not
covered by the rule snapshot's generation. A refresh landing mid-walk
meant the match was built from config that no longer existed, and the
existing check after resolve() could only discard the result: the
workflow fetch, the offerings load and the asset prewarm had already
happened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VrDQrA37Fr5XZvpT1KvD8D

* Stop the rule walk when the task is cancelled

Neither the audience lookup nor RulesEngine.evaluate throws on
cancellation, so a cancelled resolution kept decoding audiences and
evaluating predicates for every remaining rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VrDQrA37Fr5XZvpT1KvD8D

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 12:13:01 +00:00
Facundo MenzellaandClaude Opus 5 1064faafd8 feat(checkpoints): Support headless offering checkpoints (#7405)
* feat(checkpoints): Support headless offering checkpoints

Port of purchases-android #3951. When a matched checkpoint rule's workflow
is a single terminal `offering` step, resolve it to that offering and hand
it to the app instead of presenting anything.

The workflow body is now read before the offering mapping, since its shape
decides what else the rule needs. An unservable rule still resolves to
`configurationUnavailable` rather than falling through to a later rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPNip3cub65pNgGuNbU8pf

* refactor(checkpoints): Realign headless offerings with android 1c579c7

Follows purchases-android#3951 after review there settled its shape:

- rename the resolution cases to matchedWorkflow / matchedOffering
- route on the initial step's type instead of "any offering step", and
  reject a UI workflow that contains one
- drop the screen / trigger / trigger-action guards, an offering step
  renders nothing so anything else it carries is ignored

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPNip3cub65pNgGuNbU8pf

* fix(checkpoints): Don't prewarm assets for headless offering checkpoints

Prewarming takes its fonts from ui_config rather than from the workflow's
screens, so a screenless offering workflow was still downloading every
downloadable app font on a path that renders nothing. Splits the
non-prewarming read out of getWorkflow and prewarms only on the branch
that goes on to present, matching purchases-android.

Also folds the duplicated offerings lookup into one helper, flattens the
step-type branching, and reuses String.isNotEmpty (which trims newlines
too, unlike the hand-rolled check it replaces).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPNip3cub65pNgGuNbU8pf

* test(checkpoints): Skip prewarming assertions below iOS 15

scheduleAssetPrewarming is behind an availability guard, so on the older
OS versions CI runs the positive assertion would fail and the negative
one would pass for the wrong reason. Matches the existing skip in
WorkflowManagerTests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPNip3cub65pNgGuNbU8pf

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 14:16:29 +02:00
Rick dc636e5417 feat(checkpoints): evaluate checkpoint audience rules locally (#7401) 2026-08-14 17:40:52 +02:00
Rick ace2c161ac feat(checkpoints): Resolve checkpoints from remote configuration (#7385)
* feat(checkpoints): Resolve checkpoints from remote configuration

* fix(checkpoints): Guard resolution config generation

* fix(checkpoints): Distinguish unknown checkpoints

* fix(checkpoints): Report unavailable remote config

* fix(checkpoints): Propagate resolution cancellation

* Use stable unknown checkpoint identifier
2026-08-12 13:15:13 +02:00
Rick 1bf0f27c20 fix(checkpoints): Refine unknown checkpoint and workflow resolution (#7384) 2026-08-07 15:46:00 +02:00
Rick beebc52e06 feat(checkpoints): core engine (#7365)
* Implement checkpoint core engine

* Align checkpoint resolver with Android

* Decouple checkpoint engine from public API

* Keep checkpoint resolver within lint limits

* Forward checkpoint presenter to core manager

* Test checkpoints without offering metadata

* Organize checkpoint workflow resolvers

* Address checkpoint core review feedback

* Clarify checkpoint resolver dependencies

* Address remaining checkpoint core feedback

* Suppress internal checkpoint offerings diagnostics

* Centralize checkpoint no-action reason values

* Preserve offerings for checkpoint workflows
2026-08-07 10:02:33 +02:00