@revturbine/sdk 0.11.13 → 0.11.17

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.
@@ -54,6 +54,22 @@ export type { ServerActionContext, ServerActionHandler, ServerActionMap, ServerA
54
54
  export type SdkMetadata = Record<string, JsonValue>;
55
55
  /** User-provided traits or custom context (inherently untyped from customer code). */
56
56
  export type SdkTraits = Record<string, JsonValue>;
57
+ /**
58
+ * Top-level keys {@link RevTurbineCustomerSdk.identify} recognizes on a
59
+ * canonical user-context input. Anything else is dropped and reported
60
+ * (plan 168 REQ-3, plan 191 REQ-3). `satisfies` guards each entry against
61
+ * typos; `update-keys-exhaustiveness.test-d.ts` derives the reverse
62
+ * direction, so an {@link IdentifyContextInput} field identify() does not
63
+ * merge has to be acknowledged there (BL-0352).
64
+ */
65
+ declare const RECOGNIZED_IDENTIFY_KEYS: readonly ["account_id", "email", "plan", "plan_handle", "usage", "entitlements", "custom", "personalization"];
66
+ /**
67
+ * The top-level {@link IdentifyContextInput} keys
68
+ * {@link RevTurbineCustomerSdk.identify} merges into the held context (the
69
+ * entries of its recognized-key list). Exported for type-level
70
+ * exhaustiveness checks; it has no runtime value.
71
+ */
72
+ export type RecognizedIdentifyKey = (typeof RECOGNIZED_IDENTIFY_KEYS)[number];
57
73
  /** Properties attached to analytics events. */
58
74
  export type SdkEventProperties = Record<string, JsonValue>;
59
75
  export interface RevTurbineSdkProvider {
@@ -187,13 +203,55 @@ import type { RevTurbineComponentType, RevTurbineSurfaceType } from '@revt-eng/c
187
203
  * ```
188
204
  */
189
205
  export type UserContextInput = Omit<UserContext, 'id' | 'tenant_id' | 'user_id' | 'created_at' | 'updated_at'>;
206
+ /**
207
+ * User-context fields only the server may write, so the browser input types
208
+ * ({@link IdentifyContextInput}, {@link RevTurbineUserContext} and, through it,
209
+ * {@link RevTurbineUpdateInput}) omit them, and every browser entry point
210
+ * (`identify()`, `update()`, `setUserContext()`, the `user` init option)
211
+ * drops them at runtime for plain-JS callers (BL-0351, BL-0352, BL-0381).
212
+ *
213
+ * Trust is by party (D-46): the browser client never asserts these values;
214
+ * the customer's server may (a server-key user-context write, or a server
215
+ * SDK's overlay inputs), and RevTurbine's server may enrich them.
216
+ *
217
+ * - `seat_type_handle` — the user's seat-type assignment (plan 279, D-36).
218
+ * It has exactly two writers: the tenant's backend, through a server-key
219
+ * user-context upsert, and RevTurbine's own server-side enrichment in
220
+ * hosted mode. It is never accepted from `identify()`, `update()` or any
221
+ * credential the browser holds; the control plane resolves the delivered
222
+ * `seat_type` / `buyer_role` built-in dimensions from it.
223
+ * - `activity_score` / `activity_score_computed_at` — computed by the
224
+ * control plane's hourly activity-score refresh from the event stream and
225
+ * persisted on `user_contexts` (plan 180; targeting-studio-ui.md §4.1,
226
+ * Activity State). The browser receives the derived level through the
227
+ * `activity_level` built-in dimension, never the score, and never writes
228
+ * either field (D-36's principle).
229
+ * - `builtin_dimensions` — the resolved built-in segment dimensions that
230
+ * stamp the reserved `rt_<dimension>` traits (plan 279 PD-3, as amended by
231
+ * D-46; BL-0381). The only browser source is the authenticated
232
+ * `GET /api/sdk/client-context` delivery, which the SDK merges into its
233
+ * held context itself; an app-supplied value would let the browser claim
234
+ * any built-in segment (e.g. `rt.subscription_state.paid`).
235
+ * - `customer_builtin_dimensions` — the customer-set built-in dimension
236
+ * values and their per-key `override` pins (D-47; BL-0382). Written only by
237
+ * the customer's server through the server-key `POST /api/sdk/user-contexts`
238
+ * upsert (server-node `setBuiltinDimensions`); the control plane folds them
239
+ * into `builtin_dimensions` before delivery.
240
+ */
241
+ declare const SERVER_ASSIGNED_USER_CONTEXT_KEYS: readonly ["seat_type_handle", "activity_score", "activity_score_computed_at", "builtin_dimensions", "customer_builtin_dimensions"];
242
+ type ServerAssignedUserContextKey = (typeof SERVER_ASSIGNED_USER_CONTEXT_KEYS)[number];
190
243
  /**
191
244
  * Input accepted by {@link RevTurbineCustomerSdk.identify} for the canonical
192
245
  * user-context shape (plan 191). `plan_handle` is THE plan matching
193
246
  * identity — the plan's `unique_handle`; the `plan` object is display
194
247
  * metadata (name/price/period) and never participates in matching.
248
+ *
249
+ * Server-assigned fields (`seat_type_handle`, `activity_score`,
250
+ * `activity_score_computed_at`, `builtin_dimensions`) are not accepted here
251
+ * (plan 279, D-36, D-46; BL-0352, BL-0381); the tenant's backend or
252
+ * RevTurbine's enrichment writes them.
195
253
  */
196
- export type IdentifyContextInput = Partial<UserContextInput> & {
254
+ export type IdentifyContextInput = Partial<Omit<UserContextInput, ServerAssignedUserContextKey>> & {
197
255
  /** THE plan matching identity — the plan's `unique_handle` (e.g. `'pro'`). */
198
256
  plan_handle?: string;
199
257
  };
@@ -334,7 +392,11 @@ export type RevTurbineGateResult<T> = {
334
392
  * `{ plan }`) never clobbers other context. Patchable fields include `plan`,
335
393
  * `email`, `account_id`, `entitlements`, `custom`, `personalization`, `trial`,
336
394
  * the billing-recovery signals (`payment_failed` / `payment_at_risk`), `tiers`,
337
- * and `usage`.
395
+ * app-known experiment assignments (`experiments`, plan 183), and `usage`.
396
+ * Server-assigned fields (`seat_type_handle`, `activity_score`,
397
+ * `activity_score_computed_at`, `builtin_dimensions`) are not patchable
398
+ * (D-36, D-46; BL-0352, BL-0381): built-in dimension values reach the browser
399
+ * only from the authenticated client-context delivery.
338
400
  *
339
401
  * `usage` keeps its friendly `Record<string, number>` shape (absolute balances),
340
402
  * distinct from the richer per-entry `usage` on {@link RevTurbineUserContext}.
@@ -346,12 +408,21 @@ export type RevTurbineUpdateInput = Omit<RevTurbineUserContext, 'id' | 'usage'>
346
408
  /**
347
409
  * Runtime mirror of {@link RevTurbineUpdateInput}'s keys — the fields
348
410
  * `update()` patches. `satisfies` guards each entry against typos/renames;
349
- * the aliases test asserts exhaustiveness against the type, so a
350
- * schema-added context field fails the build until acknowledged here.
351
- * JS callers' keys outside this set are dropped with a dev warning rather
352
- * than merged into the context.
411
+ * `update-keys-exhaustiveness.test-d.ts` (compiled by
412
+ * `pnpm check:types:exact`) derives the reverse direction, so a
413
+ * schema-added context field fails the build until it is acknowledged here
414
+ * or omitted from the browser input types (BL-0352). JS callers' keys
415
+ * outside this set are dropped with a warning rather than merged into the
416
+ * context.
417
+ */
418
+ export declare const RECOGNIZED_UPDATE_KEYS: readonly ["usage", "account_id", "email", "email_type", "plan_handle", "plan", "trial", "payment_failed", "payment_at_risk", "tiers", "entitlements", "instances", "custom", "personalization", "experiments", "derived_config_version", "context_hash", "derived_computed_at"];
419
+ /**
420
+ * The top-level {@link RevTurbineUpdateInput} keys
421
+ * {@link RevTurbineCustomerSdk.update} patches (the entries of
422
+ * {@link RECOGNIZED_UPDATE_KEYS}). Exported for type-level exhaustiveness
423
+ * checks; it has no runtime value.
353
424
  */
354
- export declare const RECOGNIZED_UPDATE_KEYS: readonly ["usage", "account_id", "email", "email_type", "plan_handle", "plan", "trial", "payment_failed", "payment_at_risk", "tiers", "entitlements", "instances", "custom", "personalization", "derived_config_version", "context_hash", "derived_computed_at"];
425
+ export type RecognizedUpdateKey = (typeof RECOGNIZED_UPDATE_KEYS)[number];
355
426
  /** Usage snapshot entry for a usage unit. */
356
427
  export interface RevTurbineUsageSnapshotEntry {
357
428
  /** Current consumed amount for the usage unit. */
@@ -404,8 +475,27 @@ export type ServerEvaluationHydrationPayload = GeneratedServerEvaluationPayload;
404
475
  * `custom` is widened to `Record<string, unknown>` at the SDK boundary
405
476
  * for customer convenience; values are narrowed to `TraitValue` when
406
477
  * persisted via the API.
407
- */
408
- export interface RevTurbineUserContext extends Omit<UserContextInput, 'custom' | 'entitlements' | 'usage' | 'personalization' | 'derived_computed_at' | 'context_hash'> {
478
+ *
479
+ * `seat_type_handle` is omitted on purpose (plan 279, D-36). A user's seat
480
+ * type is assigned only server-side: by the tenant's backend through a
481
+ * server-key user-context upsert, or by RevTurbine's enrichment in hosted
482
+ * mode. The browser never writes it, so neither `identify()`, `update()`,
483
+ * `setUserContext()` nor the `user` init option accepts it. The browser
484
+ * receives the resolved `seat_type` / `buyer_role` as built-in dimensions
485
+ * delivered by `GET /api/sdk/client-context` instead. The server-computed
486
+ * `activity_score` / `activity_score_computed_at` are omitted on the same
487
+ * principle (BL-0352); the browser receives the derived `activity_level`
488
+ * dimension.
489
+ *
490
+ * `builtin_dimensions` is omitted too (D-46, BL-0381): the browser never
491
+ * asserts a built-in dimension value. The SDK holds the values
492
+ * `fetchClientContext` delivers and stamps the reserved `rt_<dimension>`
493
+ * segment traits from them; read them back through
494
+ * {@link RevTurbineCustomerSdk.getUserContext getUserContext()}. A plain-JS
495
+ * caller that passes any of these keys has them dropped and reported at
496
+ * runtime.
497
+ */
498
+ export interface RevTurbineUserContext extends Omit<UserContextInput, 'custom' | 'entitlements' | 'usage' | 'personalization' | 'derived_computed_at' | 'context_hash' | ServerAssignedUserContextKey> {
409
499
  /** Authenticated user identifier. When undefined, the SDK uses an anonymous ID. */
410
500
  id?: string;
411
501
  /**
@@ -434,25 +524,6 @@ export interface RevTurbineUserContext extends Omit<UserContextInput, 'custom' |
434
524
  * `string | number` when serialized.
435
525
  */
436
526
  personalization?: SdkTraits;
437
- /**
438
- * Server-evaluated built-in segment dimensions (plan 279 PD-3), one leaf
439
- * per resolved dimension keyed by its dimension key (e.g.
440
- * `subscription_state`, `region`) — see the built-in segment resolution
441
- * contract in `targeting-studio-ui.md` §4.1 for the full vocabulary.
442
- *
443
- * `fetchClientContext` delivers this from `GET /api/sdk/client-context`
444
- * and merges it the way it merges `plan_handle`: **server values overlay
445
- * app-set values**, per leaf. A dimension the server did not evaluate on a
446
- * given fetch leaves whatever the app (or a prior fetch) had set for that
447
- * leaf untouched; a dimension is simply absent when neither side has ever
448
- * set it (unknown is trait absence — plan 255).
449
- *
450
- * Feeds the reserved `rt_<dimension>` segment traits that built-in
451
- * Playbook segments match on — never read or write those trait keys
452
- * directly. Set this yourself only to seed `local_only` mode or a
453
- * server-side port, which has no client-context fetch of its own.
454
- */
455
- builtin_dimensions?: UserContextInput['builtin_dimensions'];
456
527
  }
457
528
  /** Page-level context automatically inferred or manually set. */
458
529
  export interface RevTurbinePageContext {
@@ -647,8 +718,26 @@ export interface RevTurbineTelemetryOptions {
647
718
  consent?: TelemetryConsent;
648
719
  }
649
720
  export interface RevTurbineInitOptions {
650
- /** Your RevTurbine tenant identifier. */
651
- tenantId: string;
721
+ /**
722
+ * Your RevTurbine tenant identifier — **optional whenever a
723
+ * {@link publicKey} is supplied** (BL-0335, `0.11.14`).
724
+ *
725
+ * The public key is bound to exactly one tenant, so the control plane
726
+ * resolves the tenant from the key alone and the SDK reads it back from the
727
+ * launched Playbook it fetches (the signed manifest's and the Playbook's
728
+ * `tenant_id`). Passing a tenant id as well is still accepted, but the key
729
+ * is the sole arbiter of the tenant (D-49): when the key-authenticated
730
+ * delivery names a different tenant, the SDK warns once per init
731
+ * (`tenantId <x> does not match the key's tenant <y>; using <y>`) and adopts
732
+ * the key's tenant. The control plane likewise ignores a mismatching tenant
733
+ * id (with a server-side warning); it never refuses the request.
734
+ *
735
+ * Still required when there is no public key to identify the tenant — a
736
+ * `local_only` init without `localRuntime.playbook` (with one, the SDK
737
+ * defaults it to `'local'`), or a server-key (`apiKey`) headless init. The
738
+ * SDK throws a descriptive error at init when neither is present.
739
+ */
740
+ tenantId?: string;
652
741
  /**
653
742
  // @revturbine-graph configuration:revturbine-sdk-internal:web-sdk/customer-side.ts#RevTurbineInitOptions
654
743
  * Your **public key** — the browser credential.
@@ -1091,6 +1180,17 @@ export interface RevTurbineLocalRuntimeOptions {
1091
1180
  * './revturbine.playbook.json'` passes `tsc --strict` directly, no cast
1092
1181
  * needed; the SDK validates the shape at init and fails fast on a malformed
1093
1182
  * artifact.
1183
+ *
1184
+ * Built-in segments (`rt.<dimension>.<value>`) need no definitions in the
1185
+ * file: the SDK adds them at load from RevTurbine's catalogue, with Seat Type
1186
+ * values taken from the Playbook's own `seat_types`. An authored segment
1187
+ * under the reserved `rt.` prefix is ignored.
1188
+ *
1189
+ * The definitions exist, but the values do not come from the app: the
1190
+ * browser never sets built-in dimensions (D-46, BL-0381). They reach the
1191
+ * SDK only from `GET /api/sdk/client-context` (a `clientSession` minter
1192
+ * backed by your server), so without that delivery every built-in segment
1193
+ * fails closed — a chip or rule naming one matches nobody.
1094
1194
  */
1095
1195
  playbook?: ConfigArtifact | UnvalidatedConfigArtifact;
1096
1196
  /**
@@ -1531,6 +1631,101 @@ export type RevTurbineTriggerPayload = TrialTriggerPayload | UsageTriggerPayload
1531
1631
  */
1532
1632
  export declare function createSemanticEvent(eventType: string, payload: SdkEventProperties, options?: RevTurbineEventOptions): RevTurbineSemanticEvent;
1533
1633
  export type { RevTurbineEventEnvelope } from '@revt-eng/core';
1634
+ /**
1635
+ * The Stripe Checkout `revturbine_*` metadata bag for a rendered output
1636
+ * (plan 282 REQ-5; research §6.2). String-valued and Stripe-shaped: spread it
1637
+ * into a Checkout Session's `metadata` and `subscription_data.metadata` as-is.
1638
+ *
1639
+ * A key the SDK does not hold is ABSENT, never empty or a placeholder — Stripe
1640
+ * reads an empty metadata value as an unset, and an invented id would be worse
1641
+ * than a gap on the attribution side.
1642
+ */
1643
+ export interface RevTurbineCheckoutMetadataBag {
1644
+ /** `PlacementOutput.output_id` — the rendered payload / treatment. */
1645
+ revturbine_output_id: string;
1646
+ /** The placement whose CTA led to checkout — what confirms credit to it. */
1647
+ revturbine_placement_id: string;
1648
+ /**
1649
+ * The id `identify()` set, as this SDK's own telemetry carries it: an
1650
+ * email-shaped id is hashed the same way on every lane, so the purchase
1651
+ * joins the presentation and click rows. Absent for an unidentified user.
1652
+ */
1653
+ revturbine_user_id?: string;
1654
+ /**
1655
+ * The identified account, redacted like the wire lanes. Absent when none was
1656
+ * identified — never the user-derived fallback the interaction lane labels.
1657
+ */
1658
+ revturbine_account_id?: string;
1659
+ /** `PlacementOutput.decision_id`. */
1660
+ revturbine_decision_id?: string;
1661
+ /** `PlacementOutput.rule_id` — the rule handle the interaction lanes stamp as `rule_handle`. */
1662
+ revturbine_rule_id?: string;
1663
+ /** The plan the CTA converts onto (`cta_path.plan_handle`), when it names one. */
1664
+ revturbine_plan_handle?: string;
1665
+ }
1666
+ /**
1667
+ * What {@link RevTurbineCustomerSdk.checkoutMetadata} returns: the bag to put
1668
+ * on the Checkout Session and its subscription, plus Stripe's own caller-side
1669
+ * session key.
1670
+ */
1671
+ export interface RevTurbineCheckoutMetadata {
1672
+ /**
1673
+ * The user id, for the session's `client_reference_id` — Stripe's native
1674
+ * caller-side key, which RevTurbine's identity bridge also reads. The same
1675
+ * value as `metadata.revturbine_user_id`; absent for an unidentified user.
1676
+ */
1677
+ client_reference_id?: string;
1678
+ /** Pass as BOTH `metadata` and `subscription_data.metadata`. */
1679
+ metadata: RevTurbineCheckoutMetadataBag;
1680
+ }
1681
+ /**
1682
+ * The decision context a treatment interaction joins on (plan 282 TASK-9),
1683
+ * accepted alongside {@link RevTurbineTreatmentInteractionInput} by
1684
+ * {@link RevTurbineCustomerSdk.trackTreatmentInteraction}. Both optional: a
1685
+ * caller that supplies neither sends exactly the interaction it sent before,
1686
+ * and the SDK fills them from its own output index when `payloadId` names an
1687
+ * output this SDK decided.
1688
+ */
1689
+ export interface RevTurbineTreatmentInteractionDecisionContext {
1690
+ /**
1691
+ * `PlacementOutput.decision_id` → wire `decision_id`, the key the
1692
+ * presentation row, the clickstream and the Checkout metadata bag
1693
+ * (`revturbine_decision_id`) share. Wins over the plan-144
1694
+ * `metadata.decision_id` convention when both are given.
1695
+ */
1696
+ decisionId?: string;
1697
+ /**
1698
+ * The segment handles the user was in when the treatment was presented →
1699
+ * wire `segment_handles`, the analytics join key. Handles, never minted
1700
+ * ids: the SDK's effective set comes from scaffold's `evaluateSegments`
1701
+ * plus any hosted-resolved slugs. `[]` is a real observation (segments
1702
+ * resolved, none matched); omit it when no decision was in scope.
1703
+ */
1704
+ segmentHandles?: readonly string[];
1705
+ /**
1706
+ * Minted segment ids a hosted context supplied → wire `segment_ids`.
1707
+ * Telemetry only, never the join key, and never the evaluated handles;
1708
+ * omit it in local mode.
1709
+ */
1710
+ segmentIds?: readonly string[];
1711
+ }
1712
+ /** The full input {@link RevTurbineCustomerSdk.trackTreatmentInteraction} accepts. */
1713
+ export type RevTurbineTreatmentInteractionRequest = RevTurbineTreatmentInteractionInput & RevTurbineTreatmentInteractionDecisionContext;
1714
+ /**
1715
+ * CTA actions that open a plan picker or pricing page rather than checkout
1716
+ * itself. A `cta_clicked` on an output whose CTA is one of these emits
1717
+ * `checkout_started` (plan 282 TASK-8/9) — the step between the click and
1718
+ * checkout, so the placement chain can hold
1719
+ * `success ≤ checkout_started ≤ clicked ≤ presented`.
1720
+ *
1721
+ * `open_checkout_modal` opens checkout directly ("on click") and is
1722
+ * deliberately NOT listed: that placement has no separate step, so its started
1723
+ * count equals its clicked count. `open_upgrade_modal` opens another placement
1724
+ * whose own CTA then decides which of the two it is, so it is not listed
1725
+ * either. `view_plans` is the authored alias the local resolver normalizes to
1726
+ * `navigate_to_plans`; it is kept for an output built by hand.
1727
+ */
1728
+ export declare const CHECKOUT_STARTED_CTA_ACTION_TYPES: readonly string[];
1534
1729
  /**
1535
1730
  * Every event name on the taxonomy's `control_plane` surface — emitted via
1536
1731
  * {@link RevTurbineCustomerSdk.trackControlPlaneEvent}, never the platform
@@ -1586,7 +1781,21 @@ export declare const EMITTABLE_PLATFORM_EVENT_NAMES: readonly string[];
1586
1781
  * ```
1587
1782
  */
1588
1783
  export declare class RevTurbineCustomerSdk {
1589
- private readonly tenantId;
1784
+ /**
1785
+ * The tenant the integration passed at init, if any. Optional since BL-0335:
1786
+ * with a public key the tenant is read from the launched Playbook instead —
1787
+ * see the `tenantId` getter.
1788
+ */
1789
+ private readonly configuredTenantId?;
1790
+ /**
1791
+ * Stable namespace for everything the SDK keys LOCALLY — storage keys,
1792
+ * cache/cap keys, generated placement ids. It is the configured tenant id
1793
+ * when one was passed (so existing integrations keep every persisted key
1794
+ * byte-for-byte), else a hash of the public key. It never changes during a
1795
+ * session, unlike `tenantId`, which a key-only integration learns only once
1796
+ * the Playbook loads.
1797
+ */
1798
+ private readonly tenantNamespace;
1590
1799
  /** The browser credential — `publicKey`, the only name it arrives under. */
1591
1800
  private readonly publicKey;
1592
1801
  /**
@@ -1678,6 +1887,23 @@ export declare class RevTurbineCustomerSdk {
1678
1887
  * segment cuts read as-of-event truth instead of requiring a config join.
1679
1888
  */
1680
1889
  private lastEffectiveSegmentIds;
1890
+ /**
1891
+ * The effective segment HANDLES at the last resolution — the provider
1892
+ * context's `segmentSlugs`: scaffold's `evaluateSegments` output plus any
1893
+ * hosted-resolved slugs. The analytics join key the interaction lane sends
1894
+ * as `segment_handles` (plan 282 TASK-9; Kent, 2026-09-29). Kept apart from
1895
+ * `lastEffectiveSegmentIds`, which mixes minted ids into those handles and
1896
+ * stamps the envelope's legacy `segment_ids` (BL-0462 owns that name).
1897
+ */
1898
+ private lastEffectiveSegmentHandles;
1899
+ /**
1900
+ * The minted segment ids a hosted context supplied at the last resolution
1901
+ * (`resolved.segments.segmentIds`) and nothing else — never the evaluated
1902
+ * handles. Telemetry only, sent as the interaction lane's `segment_ids`;
1903
+ * `undefined` when no hosted context supplied any, so local mode omits the
1904
+ * key entirely.
1905
+ */
1906
+ private lastHostedSegmentIds?;
1681
1907
  private pageContext;
1682
1908
  /**
1683
1909
  * Secure per-user client token (`rt_client_`, plan 157). Minted by the
@@ -1718,7 +1944,25 @@ export declare class RevTurbineCustomerSdk {
1718
1944
  private localPlacementsByLookupKey;
1719
1945
  private localEntitlementsByHandle;
1720
1946
  private localUserContextsByUserId;
1947
+ /**
1948
+ * The SDK-held trial status — written ONLY by the server-delivered /
1949
+ * SDK-derived sources (`localRuntime.initialData.trialStatus`, `hydrate()`,
1950
+ * `getTrialStatus()`, `setTrialInstances()`), always through
1951
+ * {@link writeTrialStatus}. Readers never read it directly: they read
1952
+ * {@link resolveTrialView}, which weighs it against the app-supplied
1953
+ * `userContext.trial` (BL-0403).
1954
+ */
1721
1955
  private localTrialStatus;
1956
+ /**
1957
+ * Trial write clock (BL-0403). Each source of trial state stamps the next
1958
+ * tick when it changes the trial; {@link resolveTrialView} picks the
1959
+ * source with the later stamp. `0` = that source has never written.
1960
+ */
1961
+ private trialWriteClock;
1962
+ /** Tick of the last {@link localTrialStatus} write; `0` = never supplied. */
1963
+ private trialStatusWrittenAt;
1964
+ /** Tick of the last change to `userContext.trial`; `0` = never supplied. */
1965
+ private contextTrialWrittenAt;
1722
1966
  private readonly presentationCapsByKey;
1723
1967
  private readonly usageLimitByEntitlement;
1724
1968
  private readonly usageTokenPrefixByEntitlement;
@@ -1744,6 +1988,21 @@ export declare class RevTurbineCustomerSdk {
1744
1988
  readonly impressionHistory: ImpressionHistory;
1745
1989
  constructor(options: RevTurbineInitOptions);
1746
1990
  private isLocalOnlyMode;
1991
+ /**
1992
+ * The tenant this SDK operates in: the key's tenant once a key-authenticated
1993
+ * delivery named one that differs from the configured `tenantId` (D-49),
1994
+ * else the configured `tenantId`, else the one the Playbook delivery named
1995
+ * (BL-0335). `undefined` only between init and the first Playbook load of a
1996
+ * key-only integration.
1997
+ */
1998
+ private get tenantId();
1999
+ /**
2000
+ * The tenant header for the SDK's direct control-plane calls: sent only when
2001
+ * the integration CONFIGURED a tenant id — and then the key's tenant once it
2002
+ * was adopted (D-49). The control plane ignores a mismatching value with a
2003
+ * warning. A key-only integration sends none — the key names the tenant.
2004
+ */
2005
+ private tenantHeader;
1747
2006
  private resolveConfigProvider;
1748
2007
  private refreshPlaybookSnapshot;
1749
2008
  /**
@@ -1821,6 +2080,103 @@ export declare class RevTurbineCustomerSdk {
1821
2080
  * checkEntitlement contract.
1822
2081
  */
1823
2082
  private getOrBuildPlacementResolver;
2083
+ /**
2084
+ * The single usage view placements read (BL-0402): one entry per
2085
+ * entitlement handle the SDK holds usage for, in the
2086
+ * `EntitlementProviderState.usage` shape the shared threshold gate
2087
+ * (`matchesThresholdTrigger`) and the placement token derivation consume.
2088
+ *
2089
+ * - **Which handles** — every object entry on `userContext.usage` (in
2090
+ * context order, so the first-entry shorthand tokens are unchanged), then
2091
+ * every handle reported only through `update({ usage })` /
2092
+ * `updateUsage()` / `identify(…, { usage })`.
2093
+ * - **`used`** — the app-reported balance (`usageBalances`, the most recent
2094
+ * write) wins; else the context entry's `amount`; else `0`. This is the
2095
+ * precedence `getUsage()` and `can()` already apply, so the meter, the
2096
+ * gate, and the placement agree.
2097
+ * - **`limit`** — the context entry's own `limit` wins; else the limit
2098
+ * `getUsage()` reports for the handle (the Playbook allowance for the
2099
+ * user's CURRENT plan: the plan-scoped `usage_limit` rule's `limit_value`
2100
+ * or `credits` rule's `allowance_value`); else `0`. `0` means "no
2101
+ * usable limit" (an `unlimited` or absent allowance): the threshold gate
2102
+ * fails closed on it, so no threshold placement fires.
2103
+ * - **`remaining`** — `max(0, limit − used)` when a limit is known, else `0`.
2104
+ */
2105
+ private resolveUsageView;
2106
+ /**
2107
+ * The single trial view every trial reader shares (BL-0403): the placement
2108
+ * resolver's `__providers.plan.trial*` fields (and so `trial_started` /
2109
+ * `trial_progress` / `trial_ending` / `trial_ended` / `trial_converted`
2110
+ * gating and the `{{trial_days_*}}` tokens), reverse-trial entitlement
2111
+ * grants, and a `local_only` `getTrialStatus()` with no resolver.
2112
+ *
2113
+ * Two sources hold trial state:
2114
+ *
2115
+ * - **the user context's `trial`** — what the app supplies (`user.trial` at
2116
+ * init, `update({ trial })`, `setUserContext`, a server action's
2117
+ * `userContext` result) plus the authenticated client-context delivery,
2118
+ * which merges its trial into the same field;
2119
+ * - **the SDK trial status** — `localRuntime.initialData.trialStatus`,
2120
+ * `hydrate()`'s `trial_status`, `getTrialStatus()`, `setTrialInstances()`.
2121
+ *
2122
+ * **Precedence: the most recent change wins** — the same rule the usage view
2123
+ * applies to reported balances ({@link resolveUsageView}). A server-delivered
2124
+ * or hydrated status therefore wins when it was supplied after the app's
2125
+ * trial; an app `update({ trial })` made after it wins over it. A write that
2126
+ * leaves the context trial unchanged is not a newer write, so re-sending the
2127
+ * same trial every render never displaces a fresher server status. A source
2128
+ * never supplied does not compete: with no SDK status, the context trial is
2129
+ * used; with no context trial (or after it is cleared), the SDK status is.
2130
+ */
2131
+ private resolveTrialView;
2132
+ /**
2133
+ * Write the SDK trial status (server-delivered / SDK-derived — see
2134
+ * {@link resolveTrialView}) and stamp it as the newest trial write.
2135
+ */
2136
+ private writeTrialStatus;
2137
+ /**
2138
+ * Forget the SDK-held trial state of the current user (BL-0417): the SDK
2139
+ * trial status, both {@link resolveTrialView} source markers, and the
2140
+ * lifecycle stage {@link evaluateTrialLifecycleTriggers} dedupes on — so the
2141
+ * next user starts with no trial, and a trial-less next user is not read as
2142
+ * "the trial just ended" (`trial_expired`). The context `trial` is cleared by
2143
+ * the caller along with the rest of the user context.
2144
+ */
2145
+ private clearTrialState;
2146
+ /**
2147
+ * Whether `nextUserId` names a DIFFERENT identified user than the one the
2148
+ * SDK currently holds (BL-0417). Anonymous → identified is not a switch: the
2149
+ * anonymous visitor becomes that user, so what they accumulated is kept.
2150
+ */
2151
+ private isIdentifiedUserSwitch;
2152
+ /**
2153
+ * Drop the previous user's trial and usage state on a switch to a different
2154
+ * identified user without an intervening reset (BL-0417): the SDK trial
2155
+ * status and markers, the context `trial`, the reported usage balances and
2156
+ * the context `usage`. Both feed decisions directly — the trial view and
2157
+ * the usage view — so carrying them across the merge would decide the new
2158
+ * user's trial placements, reverse-trial grants, `{{trial_days_*}}` /
2159
+ * `{{usage_*}}` tokens and usage thresholds from the old user's state.
2160
+ * The new identity's own values are merged in afterwards by the caller.
2161
+ */
2162
+ private dropPreviousUserTrialAndUsage;
2163
+ /**
2164
+ * Run the trial lifecycle evaluation against the resolved trial view
2165
+ * (BL-0417). Called after every change that can move the view — the app's
2166
+ * context trial (`user.trial` at init, `update({ trial })`,
2167
+ * `setUserContext`, a server action's context, the client-context
2168
+ * delivery), `hydrate()`, a reset or user switch, and a Playbook load that
2169
+ * enables the triggers — so `trial_midpoint` / `trial_expiring` /
2170
+ * `trial_expired` fire whichever source supplied the trial.
2171
+ *
2172
+ * The dedupe is the evaluator's own: a stage fires only when it differs
2173
+ * from the last stage seen, so re-evaluating an unchanged view — or both
2174
+ * sources reporting the same threshold — never fires twice. While the
2175
+ * triggers are off (no Playbook loaded yet, or none authoring a `trials`
2176
+ * placement) nothing is evaluated or recorded, so a trial supplied before
2177
+ * the Playbook loads still fires once it does.
2178
+ */
2179
+ private syncTrialLifecycleWithView;
1824
2180
  /**
1825
2181
  * Build a minimal provider context from the SDK's user state when no
1826
2182
  * explicit domain providers are registered. This allows the local
@@ -2042,12 +2398,46 @@ export declare class RevTurbineCustomerSdk {
2042
2398
  *
2043
2399
  * These five are exactly the tokens whose values come from the Playbook's
2044
2400
  * plan catalogue plus the recommendation strategy, so the SDK — not the
2045
- * host app — is the only thing that can fill them in. Every other token in
2046
- * the studio table (usage, trial, seats, credits) is resolved on the React
2047
- * render lane by `derivePlacementPersonalizationTokens`, which unknown
2048
- * tokens pass through untouched.
2401
+ * host app — is the only thing that can fill them in.
2402
+ *
2403
+ * The usage family (`{{usage_current}}` / `{{usage_limit}}` /
2404
+ * `{{usage_remaining}}` / `{{usage_percent}}` and their `<unit>_usage_*`
2405
+ * forms) is filled here too, from the SAME provider usage view the
2406
+ * threshold gate decided the placement with (BL-0402) — see
2407
+ * {@link usageContentTokens}. So are `{{trial_days_remaining}}` and
2408
+ * `{{trial_days_total}}`, from the same provider trial state the trial
2409
+ * trigger gate read (BL-0403) — see {@link trialContentTokens}. Every other
2410
+ * token in the studio table (seats) is resolved on the React render lane by
2411
+ * `derivePlacementPersonalizationTokens`, which unknown tokens pass through
2412
+ * untouched.
2049
2413
  */
2050
2414
  private derivedContentTokens;
2415
+ /**
2416
+ * Trial personalization tokens for decision content (BL-0403), read from the
2417
+ * provider plan state the placement was decided against — the synthesized
2418
+ * {@link resolveTrialView} projection unless a registered plan provider
2419
+ * supplies its own. Mirrors `derivePlacementPersonalizationTokens`: each
2420
+ * token is filled only when its value is a finite number, so an unknown
2421
+ * trial length leaves the token untouched rather than rendering `0`.
2422
+ */
2423
+ private trialContentTokens;
2424
+ /**
2425
+ * Usage personalization tokens for decision content, read from the provider
2426
+ * usage view the placement was decided against (BL-0402) — the synthesized
2427
+ * {@link resolveUsageView} unless a registered entitlements provider
2428
+ * supplies its own.
2429
+ *
2430
+ * Per entitlement: `<unit>_usage_current` always; `<unit>_usage_limit`,
2431
+ * `<unit>_usage_remaining` and `<unit>_usage_percent` only when a positive
2432
+ * limit is known (an unknown or unlimited allowance leaves those tokens
2433
+ * untouched rather than rendering a misleading `0`). The unit prefix and the
2434
+ * choice of entitlement for the unprefixed `usage_*` shorthand (and its
2435
+ * `current_usage` / `current_limit` / `remaining_usage` aliases) follow
2436
+ * `getPersonalizationTokens()`: handles in sorted order, the `core` unit
2437
+ * first, else the first with a limit, else the first. Percent is rounded and
2438
+ * capped at 100, as it is there.
2439
+ */
2440
+ private usageContentTokens;
2051
2441
  /**
2052
2442
  * Substitute the SDK-resolved plan/price/recommendation tokens into a
2053
2443
  * decision's content.
@@ -2417,9 +2807,19 @@ export declare class RevTurbineCustomerSdk {
2417
2807
  * upsert; an omitted field never clobbers a previously-set value. See the
2418
2808
  * advertised {@link update} alias for the full recognized-field contract.
2419
2809
  *
2810
+ * Server-assigned fields (`seat_type_handle`, `activity_score`,
2811
+ * `activity_score_computed_at`) are dropped and reported, never merged:
2812
+ * they are written only by the tenant's backend or RevTurbine's
2813
+ * enrichment (D-36, BL-0352). The parameter type does not yet reject other
2814
+ * undeclared keys the way {@link identify} and {@link update} do; that
2815
+ * `Exact<>` narrowing is deferred to `0.12.0` because it can break callers
2816
+ * that pass a wider object today.
2817
+ *
2420
2818
  * @public
2421
2819
  */
2422
2820
  setUserContext(userContext: RevTurbineUserContext): void;
2821
+ /** {@link setUserContext}'s merge, on a patch already cleared of server-assigned keys. */
2822
+ private mergeAcceptedUserContext;
2423
2823
  /**
2424
2824
  * Subscribe to user-context changes — `identify()`, `setUserContext()`,
2425
2825
  * `update()`, `updateUsage()`, and `resetIdentity()` (plan 194 REQ-3).
@@ -2721,9 +3121,21 @@ export declare class RevTurbineCustomerSdk {
2721
3121
  * lower-level counterpart to {@link dismiss}/{@link snooze}/{@link convert},
2722
3122
  * which key off a decision's `output_id` instead.
2723
3123
  *
3124
+ * `decisionId`, `segmentHandles` and `segmentIds` (plan 282 TASK-9) ride
3125
+ * the interaction onto the wire as `decision_id` / `segment_handles` /
3126
+ * `segment_ids`. All optional: given, they win; otherwise the SDK fills
3127
+ * them from the output it indexed for `payloadId` (`segment_ids` only when
3128
+ * a hosted context supplied minted ids — never in local mode); a caller
3129
+ * that supplies none and names no indexed output sends exactly what it
3130
+ * sent before.
3131
+ *
3132
+ * A `cta_clicked` on an indexed output whose CTA opens a plan picker or
3133
+ * pricing page ({@link CHECKOUT_STARTED_CTA_ACTION_TYPES}) also emits
3134
+ * `checkout_started` — never for a CTA that opens checkout directly.
3135
+ *
2724
3136
  * @public
2725
3137
  */
2726
- trackTreatmentInteraction(input: RevTurbineTreatmentInteractionInput): Promise<void>;
3138
+ trackTreatmentInteraction(input: RevTurbineTreatmentInteractionRequest): Promise<void>;
2727
3139
  getPlacementContent(placementId: string, request?: JsonObject): Promise<RevTurbinePlacementContent>;
2728
3140
  /**
2729
3141
  * Report user-context keys the SDK does not recognize, then let the caller
@@ -2773,6 +3185,15 @@ export declare class RevTurbineCustomerSdk {
2773
3185
  */
2774
3186
  private rejectLegacyPlanId;
2775
3187
  private reportUnrecognizedContextKeys;
3188
+ /**
3189
+ * Report server-assigned keys a browser entry point dropped (D-36,
3190
+ * BL-0351, BL-0352). Same once-per-session dedupe and anonymous
3191
+ * `sdk_validation_warning` lane as {@link reportUnrecognizedContextKeys},
3192
+ * with guidance that names the actual writer instead of pointing the caller
3193
+ * at `custom` — moving a server-assigned value under `custom` would not make
3194
+ * it any less browser-supplied.
3195
+ */
3196
+ private reportServerAssignedContextKeys;
2776
3197
  private normalizePlacementOutput;
2777
3198
  private validateTrialStatusShape;
2778
3199
  /**
@@ -2841,10 +3262,21 @@ export declare class RevTurbineCustomerSdk {
2841
3262
  * window, which trains people to ignore the warning.
2842
3263
  */
2843
3264
  private reportUnmatchedUsageKeys;
3265
+ /**
3266
+ * `getUserContext()`'s `trial` (BL-0417): the resolved trial view, copied,
3267
+ * or nothing when neither trial source was ever supplied (the SDK's
3268
+ * `{ in_trial: false }` default is not a fact about the user).
3269
+ */
3270
+ private userContextTrialProjection;
2844
3271
  /**
2845
3272
  * Build the full persistence-ready {@link UserContext} from the current
2846
3273
  * SDK state. Includes `tenant_id` and `user_id` required for API storage.
2847
3274
  *
3275
+ * Also projects the decision-driving fields — `trial` (the resolved trial
3276
+ * view), `tiers`, `payment_failed`, `payment_at_risk` and `instances` —
3277
+ * when held. The snapshot is a copy: changing it changes nothing in the SDK;
3278
+ * write through `update()` / `setUserContext()`.
3279
+ *
2848
3280
  * @public
2849
3281
  */
2850
3282
  getUserContext(): UserContext;
@@ -3132,6 +3564,36 @@ export declare class RevTurbineCustomerSdk {
3132
3564
  private warnOnUiPathResolverCoverageGaps;
3133
3565
  /** Route output-addressed callbacks through the canonical interaction/analytics path. */
3134
3566
  private trackOutputInteraction;
3567
+ /**
3568
+ * The Stripe Checkout `revturbine_*` metadata bag for a rendered output
3569
+ * (plan 282 REQ-5; research §6.2), keyed by the `output_id` of a decision
3570
+ * this SDK produced — what the customer's server passes when it creates the
3571
+ * Checkout Session the output's CTA leads to.
3572
+ *
3573
+ * Pass `metadata` as the session's `metadata` AND as
3574
+ * `subscription_data.metadata`. The session copy is what
3575
+ * `checkout.session.completed` carries; the subscription copy is what every
3576
+ * later `customer.subscription.*` event carries and what each invoice
3577
+ * snapshots under `parent.subscription_details.metadata`, so renewals,
3578
+ * failures and recoveries stay attributable without a `customers` row. Pass
3579
+ * `client_reference_id` as the session's `client_reference_id`, Stripe's own
3580
+ * caller-side key, which the identity bridge also reads as the user id.
3581
+ *
3582
+ * Checkout cannot set Customer metadata inline: if you let Checkout create
3583
+ * the Customer, follow up with `customers.update({ metadata })` — see the
3584
+ * billing guide.
3585
+ *
3586
+ * Every value is one this SDK holds; nothing is minted. The user id is the
3587
+ * one `identify()` set, as this SDK's own telemetry carries it (an
3588
+ * email-shaped id is hashed the same way on every lane, so the purchase
3589
+ * joins the click). The account id is present only when an account was
3590
+ * identified — never the user-derived fallback. A key the SDK does not hold
3591
+ * is absent, not empty.
3592
+ *
3593
+ * @returns `null` when no decision from this SDK produced `outputId`.
3594
+ * @public
3595
+ */
3596
+ checkoutMetadata(outputId: string): RevTurbineCheckoutMetadata | null;
3135
3597
  /**
3136
3598
  * Suppress a decision's placement for its authored dismiss cooldown.
3137
3599
  * Terminal for the window: `getPlacementDecision` will not return it again