@revturbine/sdk 0.10.4 → 0.10.6

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.
package/CHANGELOG.md CHANGED
@@ -47,6 +47,103 @@ also require a changelog entry.
47
47
 
48
48
  ---
49
49
 
50
+ ## 0.10.6
51
+
52
+ ### The trial-status PlanProvider overlay ships in the Rust crate (BL-0153)
53
+
54
+ **What changed.** `revturbine::overlay_trial_status_on_plan_provider` is a new
55
+ public function on the Rust crate: it maps a runtime `UserTrialStatus` onto the
56
+ `trial_*` fields of a resolved PlanProviderState, the fields the placement
57
+ resolver's `trial_progress` / `trial_ending` / `trial_ended` /
58
+ `trial_converted` gates and milestone supersession read. Python has always done
59
+ this inside its shipped package
60
+ (`revturbine.sdk._overlay_trial_status_on_plan_provider`); on Rust the mapping
61
+ existed only as a private helper the facade used, so a host that assembles its
62
+ own provider context — including this repo's own parity runner — had to
63
+ re-implement it. Additive: no existing signature changed.
64
+
65
+ Porting Python's mapping faithfully corrected two Rust-only divergences in the
66
+ overlay the facade applies:
67
+
68
+ | `UserTrialStatus` field | Rust before | Rust now (= TS/Python) |
69
+ |---|---|---|
70
+ | `day_number` + `days_remaining` | not derived | `trial_days_total = day_number + days_remaining`, only when both are present |
71
+ | `day_number` | written to `trial_day_number` | not written — no provider-state field reads it |
72
+ | `usage_entitlement_handle` | dropped | `trial_usage_entitlement_handle` |
73
+
74
+ `trial_days_total` is the input to the time-mode progress fallback in
75
+ `placements::trial_gating`, so before this the fallback could never fire on the
76
+ Rust port.
77
+
78
+ **Landed in.** `0.10.6`.
79
+
80
+ **Fail-closed in.** `0.10.6` — the parity facade-presence gate
81
+ (`tests/parity/facade-surface.json`) no longer allow-lists a runner-local
82
+ overlay, so a future re-implementation harness-side fails the gate.
83
+
84
+ **Proving test.** `server-rust/src/sdk.rs` `overlay_maps_every_canonical_trial_field`,
85
+ `trial_days_total_requires_both_halves`, `overlay_does_not_clobber_base_state_with_null`
86
+ (mirroring `server-python/tests/test_trial_overlay_upsert.py`), plus the
87
+ `trial_ending_days_before_end`, `trial_ended_post_expiry` and
88
+ `trial_progress_milestone_supersession` parity fixtures.
89
+
90
+ ---
91
+
92
+ ## 0.10.5
93
+
94
+ ### Trial-only local integrations get provider context (BL-0120)
95
+
96
+ **What changed.** `synthesizeProviderContext()` — the fallback that builds
97
+ `DomainProvider` state from `userContext` when no explicit domain provider is
98
+ registered — dropped an integration's trial data whenever no commercial
99
+ `plan` / `plan_handle` was also present. An app supplying **only**
100
+ `initialData.trialStatus` (or `setTrialInstances()` / `setTrialStatus()` with
101
+ no plan) got no provider context at all: `providers.plan` came back
102
+ `undefined`, so `{{trial_days_remaining}}` (and every other `trial*` token
103
+ `derivePlacementPersonalizationTokens` derives from `providers.plan`) stayed
104
+ raw, unresolved, in placement copy. Reported against the CybeDefend demo
105
+ (SDK 0.7.13): a `trial_ending` placement with `in_trial: true,
106
+ days_remaining: 3` left `{{trial_days_remaining}}` literal in the rendered
107
+ body.
108
+
109
+ Two omissions, same root cause — trial was never checked alongside
110
+ `plan` / `plan_handle` / `usage` / `tiers` / `experiments`:
111
+
112
+ - the early-return guard (`if (!plan && !planHandle && !usage && !hasTiers
113
+ && !hasExperiments) return undefined;`) treated a trial-only integration as
114
+ having no signal whatsoever;
115
+ - even past that guard, the returned `plan` key itself was gated on
116
+ `plan || planHandle` — so the trial fields (`trialActive`,
117
+ `trialDaysRemaining`, etc.) it computed were still discarded for a
118
+ plan-less trial.
119
+
120
+ Both gates now also check `hasTrial` (an explicit trial signal — the default
121
+ untouched `{ in_trial: false }` sentinel does not count, so the omitted-signal
122
+ short-circuit is otherwise unchanged for integrations that supply nothing at
123
+ all). `usage`-only, `tiers`-only, and `experiments`-only integrations already
124
+ threaded through correctly before this change; trial was the only signal
125
+ missing from both checks.
126
+
127
+ No public API change — `synthesizeProviderContext` is private, and the fix is
128
+ additive: a plan-less trial integration now gets a `providers.plan` it never
129
+ got before, with an empty `currentPlanHandle: ''` (no plan name/handle was
130
+ ever supplied to derive one from).
131
+
132
+ **Landed in** `0.10.5`. **Fail-closed in** n/a — a previously-dropped signal
133
+ is now included; nothing that resolved before stops resolving.
134
+
135
+ **Proving test:** `web-sdk/customer-side-trial-token-signal.test.ts` — a
136
+ trial-only local integration now gets a non-empty provider context with
137
+ `trialActive`/`trialDaysRemaining` set, and feeding those through
138
+ `derivePlacementPersonalizationTokens` + `resolveContent` renders
139
+ `{{trial_days_remaining}}` as `"3"` instead of leaving it literal. Includes
140
+ the positive control (trial + plan together — unchanged), the negative
141
+ control (no trial data at all — the token stays absent, never coerced to
142
+ `"0"`), and a sibling check confirming usage-only integrations were already
143
+ unaffected by the guard.
144
+
145
+ ---
146
+
50
147
  ## 0.10.4
51
148
 
52
149
  ### Every payload is a selection candidate, not just the first (BL-0122)
@@ -94,6 +191,8 @@ placement where payload 1 is not the payload a given user matches. Those users
94
191
  now see the payload their chips select — which is the fix, and is a live
95
192
  behaviour change for such configs. Single-payload placements are unaffected.
96
193
 
194
+ ---
195
+
97
196
  ## 0.10.3
98
197
 
99
198
  ### The Rust port gains the decision surfaces Python already shipped (BL-0145)
@@ -124,6 +223,8 @@ and the crate could lack the capability entirely without a single fixture
124
223
  going red. The runner now calls `revturbine::plans`, and the inline copies are
125
224
  deleted; all 102 comparisons stay byte-identical.
126
225
 
226
+ ---
227
+
127
228
  ## 0.10.2
128
229
 
129
230
  ### Local mode resolves placements by slot id (BL-0119)