@wireai/activation 0.9.0 → 0.9.1

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.
@@ -20,17 +20,42 @@ import { k as ReviewTarget, g as ReviewDecisionResponse, j as ReviewSubmission }
20
20
  * a missing `fetch`, or a network failure is swallowed and the call returns immediately.
21
21
  */
22
22
  declare const submitReview: (target: ReviewTarget | undefined, review: ReviewSubmission) => void;
23
- /** Options for the best-effort review decision fetch. */
23
+ /**
24
+ * Options for the best-effort review decision fetch.
25
+ *
26
+ * ── WHAT THE DEPLOYED SERVER ACTUALLY READS (verified 2026-07-17) ────────────────────────
27
+ *
28
+ * `GET /v1/reviews/decision` declares exactly two query params — `session_id` and
29
+ * `device_key` — plus the `Authorization` header. That is the whole wire. Verified against
30
+ * the deployed OpenAPI schema, not against intent.
31
+ *
32
+ * This type is CLOSED on purpose. Hosts that hand-rolled this fetch invented `user_id` and
33
+ * `session_count` query params believing "the server ignores what it doesn't read, so passing
34
+ * it is always safe". Both are no-ops: the route declares neither. `session_count` is real, but
35
+ * only on the questionnaire POST body — which is precisely how it copy-pasted its way into a
36
+ * reviews call site and sat there doing nothing. They cost a wire lie — a call site that reads as though
37
+ * identity and a session counter reach the firing brain when neither does. So they are not
38
+ * offered here. Pass identity as `deviceKey`; the session count the server reasons about is the
39
+ * one IT derives from the event stream keyed by `deviceKey`, not one the client asserts.
40
+ *
41
+ * The client-side session floor is a LOCAL rule, not a wire param: use `ReviewConfig.minSessions`
42
+ * (evaluated by `useReviewGate` against the kit's own `wire_review_<id>_sessions` counter).
43
+ */
24
44
  interface FetchReviewDecisionOptions {
25
45
  /**
26
46
  * The onboarding session id, when there IS one. OPTIONAL on purpose: the review gate lives on
27
47
  * the home feed, where a user legitimately has no onboarding session. `deviceKey` is the real
28
- * identity for this call. (Today's prod still requires `session_id` and answers 422 without it;
29
- * the server relaxation is unmerged. A 422 is a non-2xx, so it returns null like any other
30
- * failure, and the local rules stand. Permissive wire: do NOT make this required here.)
48
+ * identity for this call. (The server relaxed `session_id` to optional on 2026-07-16 and the
49
+ * relaxation IS deployed the route no longer 422s without it. Permissive wire: do NOT make
50
+ * this required here.)
31
51
  */
32
52
  sessionId?: string;
33
- /** A stable, non-PII device id. The decision endpoint reads it for cooldown + min-sessions. */
53
+ /**
54
+ * A stable, non-PII device id — THE identity for this call. The decision endpoint reads it for
55
+ * cooldown + min-sessions, and it is the key the server groups a device's events under. A host
56
+ * whose own identity is a user id passes that id here rather than reaching for a `user_id`
57
+ * param the route does not declare.
58
+ */
34
59
  deviceKey?: string;
35
60
  }
36
61
  /**
@@ -57,12 +82,29 @@ interface FetchReviewDecisionOptions {
57
82
  * • else → null, and ONLY then. Null means "the server genuinely has no opinion", which is
58
83
  * the one case where falling back to local rules is correct.
59
84
  *
60
- * Never throws: unreachable, non-2xx (incl. today's 422), bad JSON, or a missing `fetch` all
61
- * resolve to null. The kit does not call this internally; a host awaits it and passes the
62
- * result straight to `useReviewGate({ decision })`.
85
+ * Never throws: unreachable, non-2xx, bad JSON, or a missing `fetch` all resolve to null. The
86
+ * kit does not call this internally; a host awaits it and passes the result straight to
87
+ * `useReviewGate({ decision })`.
88
+ *
89
+ * ── DON'T RACE THIS AGAINST A LOCAL TIMER ────────────────────────────────────────────────
90
+ *
91
+ * The return type is `ReviewDecisionResponse`, which carries `arm` alongside `{fire, reason}`.
92
+ * Hand the WHOLE object to the gate and echo `arm` into the submission's `meta.firing_arm`;
93
+ * narrowing it to `{fire, reason}` on the way through silently kills per-arm attribution
94
+ * across a reweighting of the experiment.
95
+ *
96
+ * A host that starts its own dwell timer in parallel with this fetch has built a race a slow
97
+ * server loses: the timer fires, the local rules show the prompt, and the `{fire:false}` still
98
+ * in flight arrives too late to stop it. Do not hand-roll that. `useReviewGate` already owns
99
+ * the wait — set `ReviewConfig.timeoutFallbackMs` and the local rules stay parked until either
100
+ * the decision lands or the window expires, whichever comes first.
63
101
  *
64
102
  * const decision = await fetchReviewDecision(target, { deviceKey });
65
- * const gate = useReviewGate({ config, decision: decision ?? undefined, storage });
103
+ * const gate = useReviewGate({
104
+ * config: { id: "home", minSessions: 2, timeoutFallbackMs: 3000 },
105
+ * decision: decision ?? undefined, // pass it whole — keep `arm`
106
+ * storage,
107
+ * });
66
108
  */
67
109
  declare const fetchReviewDecision: (target: ReviewTarget | undefined, options?: FetchReviewDecisionOptions) => Promise<ReviewDecisionResponse | null>;
68
110
  /** Options for a reported app event. `deviceKey` groups a device's sessions server-side. */
@@ -20,17 +20,42 @@ import { k as ReviewTarget, g as ReviewDecisionResponse, j as ReviewSubmission }
20
20
  * a missing `fetch`, or a network failure is swallowed and the call returns immediately.
21
21
  */
22
22
  declare const submitReview: (target: ReviewTarget | undefined, review: ReviewSubmission) => void;
23
- /** Options for the best-effort review decision fetch. */
23
+ /**
24
+ * Options for the best-effort review decision fetch.
25
+ *
26
+ * ── WHAT THE DEPLOYED SERVER ACTUALLY READS (verified 2026-07-17) ────────────────────────
27
+ *
28
+ * `GET /v1/reviews/decision` declares exactly two query params — `session_id` and
29
+ * `device_key` — plus the `Authorization` header. That is the whole wire. Verified against
30
+ * the deployed OpenAPI schema, not against intent.
31
+ *
32
+ * This type is CLOSED on purpose. Hosts that hand-rolled this fetch invented `user_id` and
33
+ * `session_count` query params believing "the server ignores what it doesn't read, so passing
34
+ * it is always safe". Both are no-ops: the route declares neither. `session_count` is real, but
35
+ * only on the questionnaire POST body — which is precisely how it copy-pasted its way into a
36
+ * reviews call site and sat there doing nothing. They cost a wire lie — a call site that reads as though
37
+ * identity and a session counter reach the firing brain when neither does. So they are not
38
+ * offered here. Pass identity as `deviceKey`; the session count the server reasons about is the
39
+ * one IT derives from the event stream keyed by `deviceKey`, not one the client asserts.
40
+ *
41
+ * The client-side session floor is a LOCAL rule, not a wire param: use `ReviewConfig.minSessions`
42
+ * (evaluated by `useReviewGate` against the kit's own `wire_review_<id>_sessions` counter).
43
+ */
24
44
  interface FetchReviewDecisionOptions {
25
45
  /**
26
46
  * The onboarding session id, when there IS one. OPTIONAL on purpose: the review gate lives on
27
47
  * the home feed, where a user legitimately has no onboarding session. `deviceKey` is the real
28
- * identity for this call. (Today's prod still requires `session_id` and answers 422 without it;
29
- * the server relaxation is unmerged. A 422 is a non-2xx, so it returns null like any other
30
- * failure, and the local rules stand. Permissive wire: do NOT make this required here.)
48
+ * identity for this call. (The server relaxed `session_id` to optional on 2026-07-16 and the
49
+ * relaxation IS deployed the route no longer 422s without it. Permissive wire: do NOT make
50
+ * this required here.)
31
51
  */
32
52
  sessionId?: string;
33
- /** A stable, non-PII device id. The decision endpoint reads it for cooldown + min-sessions. */
53
+ /**
54
+ * A stable, non-PII device id — THE identity for this call. The decision endpoint reads it for
55
+ * cooldown + min-sessions, and it is the key the server groups a device's events under. A host
56
+ * whose own identity is a user id passes that id here rather than reaching for a `user_id`
57
+ * param the route does not declare.
58
+ */
34
59
  deviceKey?: string;
35
60
  }
36
61
  /**
@@ -57,12 +82,29 @@ interface FetchReviewDecisionOptions {
57
82
  * • else → null, and ONLY then. Null means "the server genuinely has no opinion", which is
58
83
  * the one case where falling back to local rules is correct.
59
84
  *
60
- * Never throws: unreachable, non-2xx (incl. today's 422), bad JSON, or a missing `fetch` all
61
- * resolve to null. The kit does not call this internally; a host awaits it and passes the
62
- * result straight to `useReviewGate({ decision })`.
85
+ * Never throws: unreachable, non-2xx, bad JSON, or a missing `fetch` all resolve to null. The
86
+ * kit does not call this internally; a host awaits it and passes the result straight to
87
+ * `useReviewGate({ decision })`.
88
+ *
89
+ * ── DON'T RACE THIS AGAINST A LOCAL TIMER ────────────────────────────────────────────────
90
+ *
91
+ * The return type is `ReviewDecisionResponse`, which carries `arm` alongside `{fire, reason}`.
92
+ * Hand the WHOLE object to the gate and echo `arm` into the submission's `meta.firing_arm`;
93
+ * narrowing it to `{fire, reason}` on the way through silently kills per-arm attribution
94
+ * across a reweighting of the experiment.
95
+ *
96
+ * A host that starts its own dwell timer in parallel with this fetch has built a race a slow
97
+ * server loses: the timer fires, the local rules show the prompt, and the `{fire:false}` still
98
+ * in flight arrives too late to stop it. Do not hand-roll that. `useReviewGate` already owns
99
+ * the wait — set `ReviewConfig.timeoutFallbackMs` and the local rules stay parked until either
100
+ * the decision lands or the window expires, whichever comes first.
63
101
  *
64
102
  * const decision = await fetchReviewDecision(target, { deviceKey });
65
- * const gate = useReviewGate({ config, decision: decision ?? undefined, storage });
103
+ * const gate = useReviewGate({
104
+ * config: { id: "home", minSessions: 2, timeoutFallbackMs: 3000 },
105
+ * decision: decision ?? undefined, // pass it whole — keep `arm`
106
+ * storage,
107
+ * });
66
108
  */
67
109
  declare const fetchReviewDecision: (target: ReviewTarget | undefined, options?: FetchReviewDecisionOptions) => Promise<ReviewDecisionResponse | null>;
68
110
  /** Options for a reported app event. `deviceKey` groups a device's sessions server-side. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wireai/activation",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "private": false,
5
5
  "description": "Premium, fully-themable drop-in AI onboarding kit for React Native / Expo, on top of the open-source wireai-rn SDK.",
6
6
  "author": "Malik Chohra <malik@getwireai.com>",
@@ -39,17 +39,42 @@ export const submitReview = (
39
39
  }
40
40
  };
41
41
 
42
- /** Options for the best-effort review decision fetch. */
42
+ /**
43
+ * Options for the best-effort review decision fetch.
44
+ *
45
+ * ── WHAT THE DEPLOYED SERVER ACTUALLY READS (verified 2026-07-17) ────────────────────────
46
+ *
47
+ * `GET /v1/reviews/decision` declares exactly two query params — `session_id` and
48
+ * `device_key` — plus the `Authorization` header. That is the whole wire. Verified against
49
+ * the deployed OpenAPI schema, not against intent.
50
+ *
51
+ * This type is CLOSED on purpose. Hosts that hand-rolled this fetch invented `user_id` and
52
+ * `session_count` query params believing "the server ignores what it doesn't read, so passing
53
+ * it is always safe". Both are no-ops: the route declares neither. `session_count` is real, but
54
+ * only on the questionnaire POST body — which is precisely how it copy-pasted its way into a
55
+ * reviews call site and sat there doing nothing. They cost a wire lie — a call site that reads as though
56
+ * identity and a session counter reach the firing brain when neither does. So they are not
57
+ * offered here. Pass identity as `deviceKey`; the session count the server reasons about is the
58
+ * one IT derives from the event stream keyed by `deviceKey`, not one the client asserts.
59
+ *
60
+ * The client-side session floor is a LOCAL rule, not a wire param: use `ReviewConfig.minSessions`
61
+ * (evaluated by `useReviewGate` against the kit's own `wire_review_<id>_sessions` counter).
62
+ */
43
63
  export interface FetchReviewDecisionOptions {
44
64
  /**
45
65
  * The onboarding session id, when there IS one. OPTIONAL on purpose: the review gate lives on
46
66
  * the home feed, where a user legitimately has no onboarding session. `deviceKey` is the real
47
- * identity for this call. (Today's prod still requires `session_id` and answers 422 without it;
48
- * the server relaxation is unmerged. A 422 is a non-2xx, so it returns null like any other
49
- * failure, and the local rules stand. Permissive wire: do NOT make this required here.)
67
+ * identity for this call. (The server relaxed `session_id` to optional on 2026-07-16 and the
68
+ * relaxation IS deployed the route no longer 422s without it. Permissive wire: do NOT make
69
+ * this required here.)
50
70
  */
51
71
  sessionId?: string;
52
- /** A stable, non-PII device id. The decision endpoint reads it for cooldown + min-sessions. */
72
+ /**
73
+ * A stable, non-PII device id — THE identity for this call. The decision endpoint reads it for
74
+ * cooldown + min-sessions, and it is the key the server groups a device's events under. A host
75
+ * whose own identity is a user id passes that id here rather than reaching for a `user_id`
76
+ * param the route does not declare.
77
+ */
53
78
  deviceKey?: string;
54
79
  }
55
80
 
@@ -77,12 +102,29 @@ export interface FetchReviewDecisionOptions {
77
102
  * • else → null, and ONLY then. Null means "the server genuinely has no opinion", which is
78
103
  * the one case where falling back to local rules is correct.
79
104
  *
80
- * Never throws: unreachable, non-2xx (incl. today's 422), bad JSON, or a missing `fetch` all
81
- * resolve to null. The kit does not call this internally; a host awaits it and passes the
82
- * result straight to `useReviewGate({ decision })`.
105
+ * Never throws: unreachable, non-2xx, bad JSON, or a missing `fetch` all resolve to null. The
106
+ * kit does not call this internally; a host awaits it and passes the result straight to
107
+ * `useReviewGate({ decision })`.
108
+ *
109
+ * ── DON'T RACE THIS AGAINST A LOCAL TIMER ────────────────────────────────────────────────
110
+ *
111
+ * The return type is `ReviewDecisionResponse`, which carries `arm` alongside `{fire, reason}`.
112
+ * Hand the WHOLE object to the gate and echo `arm` into the submission's `meta.firing_arm`;
113
+ * narrowing it to `{fire, reason}` on the way through silently kills per-arm attribution
114
+ * across a reweighting of the experiment.
115
+ *
116
+ * A host that starts its own dwell timer in parallel with this fetch has built a race a slow
117
+ * server loses: the timer fires, the local rules show the prompt, and the `{fire:false}` still
118
+ * in flight arrives too late to stop it. Do not hand-roll that. `useReviewGate` already owns
119
+ * the wait — set `ReviewConfig.timeoutFallbackMs` and the local rules stay parked until either
120
+ * the decision lands or the window expires, whichever comes first.
83
121
  *
84
122
  * const decision = await fetchReviewDecision(target, { deviceKey });
85
- * const gate = useReviewGate({ config, decision: decision ?? undefined, storage });
123
+ * const gate = useReviewGate({
124
+ * config: { id: "home", minSessions: 2, timeoutFallbackMs: 3000 },
125
+ * decision: decision ?? undefined, // pass it whole — keep `arm`
126
+ * storage,
127
+ * });
86
128
  */
87
129
  export const fetchReviewDecision = async (
88
130
  target: ReviewTarget | undefined,