@cross-deck/react-native 1.0.0 → 1.5.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,103 @@ All notable changes to `@cross-deck/react-native` will be documented
4
4
  here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.5.1] — 2026-05-27
8
+
9
+ `crossdeck.contract_failed` is now single-fire to a dedicated
10
+ reliability endpoint instead of the customer's `track()` pipeline.
11
+ Independent-controller flow per Privacy Policy §6; schema-locked by
12
+ `contracts/diagnostics/contract-failed-payload-schema-lock.json`.
13
+ `ContractFailureInput.extra` removed (schema-lock forbids unbounded
14
+ fields); `ContractFailureInput.deviceClass` added.
15
+
16
+ ## [1.5.0] — 2026-05-26
17
+
18
+ Minor — `CrossdeckContracts` + `reportContractFailure(...)` ship as a
19
+ new public surface on every SDK simultaneously. Additive only; no
20
+ behavioural change to existing APIs.
21
+
22
+ **Added:**
23
+
24
+ - **`CrossdeckContracts` namespace** — typed access to the bank-grade
25
+ contract registry. Methods: `all()`, `allIncludingHistorical()`,
26
+ `byId(id)`, `byPillar(pillar)`, `withStatus(status)`,
27
+ `findByTestName(name)`. Properties: `sdkVersion`, `bundledIn`
28
+ (e.g. `"@cross-deck/react-native@1.5.0"`).
29
+ - **`Contract` type + `ContractPillar` / `ContractStatus` /
30
+ `ContractAppliesTo` unions + `ContractTestRef` + `ContractFailureInput`
31
+ interfaces** exported from the top-level entry. Treated as
32
+ binary-stable.
33
+ - **`Crossdeck.reportContractFailure(input)` method** — fires a typed
34
+ `crossdeck.contract_failed` event through the standard `track()`
35
+ pipeline when a contract test asserts and fails. Wire properties:
36
+ `contract_id`, `sdk_version` (auto-stamped), `sdk_platform`
37
+ (auto-stamped to `"react-native"`), `failure_reason`, `run_context`
38
+ (`ci` | `dogfood` | `customer-app`), `run_id`, plus optional
39
+ `test_file` / `test_name`.
40
+
41
+ **Fixed:**
42
+
43
+ - Pre-hydration `track()` calls now correctly snapshot the
44
+ call-time `sessionId` and thread it through the deferred
45
+ enrichment body. Previously, two `track()` calls separated by
46
+ `setSessionId(...)` BEFORE hydration completed would both pick up
47
+ whatever `sessionId` was current at hydration resolution — silently
48
+ rewriting the first event with the second event's state. This
49
+ contract is RN-specific (Web/Node have no hydration window).
50
+
51
+ **Changed:**
52
+
53
+ - Contract registry source files migrated to camelCase keys
54
+ (`appliesTo`, `codeRef`, `testRef`, `registeredAt`,
55
+ `firstRegisteredIn`). The bundled `contracts.json` sidecar uses
56
+ the new keys; `bundledIn` is build-stamped, never in source.
57
+
58
+ ## [1.4.2] — 2026-05-26
59
+
60
+ Patch — wire `bundleId` + `packageName` (per-platform identity-
61
+ lock fields declared on `CrossdeckOptions` since v1.3.0) into
62
+ the `InternalState` opts merge. tsc accepted the missing fields
63
+ in monorepo CI because the monorepo test workflow doesn't lint
64
+ the RN SDK — only the Web SDK gets type-checked. The public
65
+ crossdeck-react-native publish workflow DOES run `npm run lint`
66
+ and aborted with TS2322. Fix: default both to empty string in
67
+ the opts initialiser (HTTP layer skips the header when empty;
68
+ backend rejects with bundle_id_not_allowed /
69
+ package_name_not_allowed at first request if the project
70
+ requires the lock — intentional fail-closed). v1.4.1 was
71
+ tagged on crossdeck-react-native but never reached npm.
72
+ **No SDK code changes vs v1.4.0 / v1.4.1**.
73
+
74
+ ## [1.4.1] — 2026-05-26
75
+
76
+ Patch — add automated npm publish workflow to the public
77
+ `crossdeck-react-native` repo so future `vX.Y.Z` tag pushes
78
+ auto-publish to npm via OIDC Trusted Publishing (matches the
79
+ existing `crossdeck-web` pattern). No SDK code changes vs v1.4.0.
80
+
81
+ **Operator note:** npmjs.com Trusted Publisher rule must be
82
+ configured for `crossdeck-react-native` (owner: VistaApps-za,
83
+ workflow: publish.yml) before the OIDC publish succeeds. First
84
+ publish after this lands will fail with an auth error if the
85
+ rule is missing — that's the prompt to configure it.
86
+
87
+ ## [1.4.0] — 2026-05-26
88
+
89
+ **Bank-grade reconciliation release.** Joined the v1.4.0 release line with the rest of the Crossdeck SDK suite. 6-pillar KPMG-style audit closed; every behavioural guarantee registered in the monorepo's `contracts/` directory with a CI-enforced audit job.
90
+
91
+ ### Added
92
+
93
+ - **Per-user entitlement cache isolation.** Storage key is now `crossdeck:entitlements:<sha256(userId)>` — a user-switch on a shared device cannot physically read prior user's cached entitlements even if the in-memory clear is somehow skipped. `reset()` wipes EVERY per-user slot via the persisted index. New pure-JS SHA-256 helper.
94
+ - **Deterministic `Idempotency-Key` on `syncPurchases()`** — same JWS/purchaseToken → same key. Cross-SDK parity oracle CI-pinned.
95
+ - **`PurchaseResult.idempotent_replay?: boolean`** — true when the backend replayed a cached response.
96
+ - **`purchase.completed` event on every successful `syncPurchases()`** — funnel parity with native auto-track.
97
+ - **`setSessionId(sessionId: string | null)`** — host-driven session lifecycle. Call from your AppState change listener so every `track()` event carries the `sessionId` property — funnel parity with the web SDK.
98
+
99
+ ### Changed
100
+
101
+ - **`init()` re-entry now drains the prior `EventQueue`'s pending timer** before swapping `this.state`. Pre-1.4.0 the timer fired AFTER the state swap, sending old-init events under new-init identity.
102
+ - **Default event-queue flush interval is now 2000ms** (was 5000ms) — cross-SDK parity.
103
+
7
104
  ## [1.0.0] — 2026-05-24
8
105
 
9
106
  First public release. Built bank-grade from day one — every audit
package/README.md CHANGED
@@ -56,10 +56,23 @@ Every Crossdeck SDK ships these patterns by default:
56
56
  customer reads as Pro on the FIRST `isEntitled()` after `init()`,
57
57
  even on a cold launch with no network. A Crossdeck outage can
58
58
  never fail a paying customer down to free.
59
+ - **Per-user cache isolation (v1.4.0).** Every `identify(userId)`
60
+ switches the entitlement cache to a per-user AsyncStorage slot
61
+ (`crossdeck:entitlements:<sha256(userId)>`) and unconditionally
62
+ wipes the in-memory snapshot. A user-switch on a shared device
63
+ CANNOT cross-read a prior user's cache, even if the in-memory
64
+ clear is somehow skipped — the storage keys are physically
65
+ separate. `reset()` then wipes every per-user slot on the device
66
+ (logout-grade).
59
67
  - **Queue durability + Stripe-style Idempotency-Key reuse.** Events
60
68
  spliced for a flush persist to AsyncStorage with the in-flight
61
69
  batch attached, so an app crash mid-flight replays the batch on
62
70
  the next launch. Backend dedupes on `(projectId, eventId)`.
71
+ - **Deterministic Idempotency-Key on `syncPurchases` (v1.4.0).**
72
+ Same signed transaction → same key → backend short-circuits
73
+ with `idempotent_replay: true` on retry. A network blip or app
74
+ crash mid-flight that re-fires the same purchase never
75
+ double-processes.
63
76
  - **4xx hard-stop.** Permanent failures (401 key revoked, 400/422
64
77
  schema, 403 permission, 404 endpoint) drop the batch + fire
65
78
  `onPermanentFailure` + `console.error` regardless of debug mode.
@@ -144,6 +157,60 @@ Crossdeck.diagnostics();
144
157
  // }
145
158
  ```
146
159
 
160
+ ## Bank-grade contracts
161
+
162
+ The SDK ships its own contracts registry — every behavioural guarantee the SDK makes (per-user cache isolation, deterministic Idempotency-Key, queue durability, etc.) lives in `contracts/**/*.json` at the monorepo root and is **bundled into every release**. The customer's lockfile pins SDK code + contracts atomically — drift between what the SDK does and what it claims is structurally impossible. See [`contracts/README.md`](https://github.com/VistaApps-za/crossdeck/blob/main/contracts/README.md) for the full architecture.
163
+
164
+ ### `CrossdeckContracts` — typed access to the bundled registry
165
+
166
+ ```ts
167
+ import { CrossdeckContracts } from "@cross-deck/react-native";
168
+
169
+ CrossdeckContracts.all(); // enforced contracts only
170
+ CrossdeckContracts.allIncludingHistorical(); // + proposed + retired
171
+ CrossdeckContracts.byId("per-user-cache-isolation");
172
+ CrossdeckContracts.byPillar("entitlements");
173
+ CrossdeckContracts.withStatus("proposed");
174
+ CrossdeckContracts.findByTestName("identify(B) makes A's entitlements unreachable from in-memory");
175
+ CrossdeckContracts.sdkVersion; // "1.5.0"
176
+ CrossdeckContracts.bundledIn; // "@cross-deck/react-native@1.5.0"
177
+ ```
178
+
179
+ The `Contract` type is exported alongside; the binary-stability promise is documented in [`contracts/README.md`](https://github.com/VistaApps-za/crossdeck/blob/main/contracts/README.md).
180
+
181
+ ### `Crossdeck.reportContractFailure(input)` — surface contract test failures
182
+
183
+ When a contract test asserts and fails — in your CI, a dogfood run, or a customer integration test — fire a typed `crossdeck.contract_failed` event over the **Crossdeck reliability channel**. This is one-way operational telemetry to the Crossdeck operations team (Privacy Policy §6, "Flow B"); it never enters your `track()` pipeline, never shows in your dashboard, never bills against your event quota. The wire shape is schema-locked at [`contracts/diagnostics/contract-failed-payload-schema-lock.json`](https://github.com/VistaApps-za/crossdeck/blob/main/contracts/diagnostics/contract-failed-payload-schema-lock.json):
184
+
185
+ ```ts
186
+ Crossdeck.reportContractFailure({
187
+ contractId: "per-user-cache-isolation",
188
+ failureReason: "expected isolation across user switch, got cross-read",
189
+ runContext: __DEV__ ? "dogfood" : "ci",
190
+ runId: process.env.GITHUB_RUN_ID ?? Date.now().toString(36),
191
+ testRef: {
192
+ file: "tests/entitlement-cache-isolation.test.ts",
193
+ name: "identify(B) makes A's entitlements unreachable from in-memory",
194
+ },
195
+ });
196
+ ```
197
+
198
+ No new endpoint, no special ingest path — the event lands in the same pipeline every other `track()` call does. It surfaces immediately in the dashboard's live event feed, the breakdown chart (group by `contract_id`, `sdk_platform`), and any alert rule with `event = crossdeck.contract_failed`.
199
+
200
+ Properties stamped on the wire:
201
+
202
+ | Property | Source |
203
+ |----------|--------|
204
+ | `contract_id` | caller |
205
+ | `sdk_version`, `sdk_platform` | auto-stamped (`@cross-deck/react-native` ships `sdk_platform: "react-native"`) |
206
+ | `failure_reason`, `run_context`, `run_id` | caller |
207
+ | `test_file`, `test_name` | set when `testRef` is provided |
208
+ | `device_class` | optional, set by caller (categorical bucket — e.g. `"ios-phone"`, `"android-tablet"`) |
209
+
210
+ The wire shape is schema-locked at [`contracts/diagnostics/contract-failed-payload-schema-lock.json`](https://github.com/VistaApps-za/crossdeck/blob/main/contracts/diagnostics/contract-failed-payload-schema-lock.json); per-SDK assertion tests gate it on every release. Free-form `extra` keys are not accepted — adding a field requires an amendment to the schema-lock contract first.
211
+
212
+ For per-test-framework hooks see [`contracts/README.md` § Reporting contract failures](https://github.com/VistaApps-za/crossdeck/blob/main/contracts/README.md#reporting-contract-failures-back-to-crossdeck).
213
+
147
214
  ## Documentation
148
215
 
149
216
  - [Full SDK reference](https://cross-deck.com/docs/react-native-sdk)