audit flagged is closed in this release. Plus one critical
cross-SDK canonical rename so the Swift identity surface matches
the Web/Node/RN role-model contract exactly.
The v1.0.0 identify signature drifted from Web/Node/RN. v1.0.1
restores zero-drift parity. Migration is a one-line change:
```diff
- try? cd.identify(customerId: "user_847", traits: ["email": "wes@example.com", "plan": "pro"])
+ try? cd.identify(userId: "user_847", email: "wes@example.com", traits: ["plan": "pro"])
```
Specifically:
- `customerId:` → `userId:`. The previous name collided with
`crossdeckCustomerId` (the cdcust_… canonical handle), confusing
the mental model. The Web/Node/RN SDKs all use `userId`.
- `email` is now a first-class top-level argument. Previously it
was buried inside `traits` and missed the bank-grade
identity-merge that the Web SDK gets when email is shipped
separately. Now hoisted to the wire as `$email` on the
`$identify` event, matching Web/Node/RN.
- Internal `customerId` field on `Identity` renamed to
`developerUserId` everywhere — the same name Web/Node/RN's
`Diagnostics.developerUserId` uses.
- Wire event field renamed from `customer_id` to
`developer_user_id` (also matches what the backend ingest
expects).
- `EntitlementSnapshot.customerId` → `developerUserId`.
- `Identity.setCustomerIdSync(...)` → `setDeveloperUserIdSync(...)`.
- Error code `missing_customer_id` → `missing_user_id`.
The Swift SDK doc now ships native auth-provider code blocks for
Sign In with Apple, Firebase Auth iOS, and Auth0 iOS, matching
the Web SDK doc's coverage of Firebase / NextAuth / Clerk /
Supabase / Auth0 / custom backends.
- `Crossdeck.isEntitled(_:)` — synchronous bool check scoped to the
currently identified customer. Safe to call from SwiftUI bodies
and UIKit tap handlers. Never blocks on network.
- `Crossdeck.entitlementsForCurrentCustomer()` — synchronous set
read. Returns nil if no customer is identified or the cache is
cold for them.
- Internally backed by NSLock-protected mirror boxes on
`EntitlementCache`, `Identity`, `SuperProperties`, and
`ConsentManager`. Every actor mutation updates its sync mirror
atomically; reads acquire the lock only.
- **NSException handler now chains into the prior handler.**
Previous v1.0.0 overwrote the global handler, silently breaking
Crashlytics / Sentry / Bugsnag for any consumer who turned on
`captureUncaughtExceptions`. `ErrorCapture.install` now captures
`NSGetUncaughtExceptionHandler()` before registering ours and
invokes the prior handler after our snapshot.
- **PII scrubber runs on `$error` events.** Previously the error
pipeline bypassed the scrubber — a `try?` that surfaced
`"user jane@example.com not found"` shipped raw. Now every
scrubbable field on the wire `$error` payload (message, stack
symbols, breadcrumb messages + data) is run through the
configured scrubber when `consent.scrubPII` is true.
- **Breadcrumbs attached to `$error` events.** Previously collected
but dropped before enqueue. Now ship as
`error.breadcrumbs: [{timestamp_ms, category, level, message, data}]`
on the wire payload.
- **`identify(...)` unconditionally clears the entitlement cache.**
Previous v1.0.0 only cleared on `didChange || priorId == nil`.
Now identifies always clear, matching the documented contract
that prevents stale entitlement leaks across customer switches.
- **Self-request skip wired into `captureError(_:)`.** Errors whose
URL host matches the configured ingest endpoint are dropped
before processing — closes the feedback loop where a custom-
middleware-wrapped ingest failure would generate an `$error`
event that itself fails, ad infinitum.
- **`track()` / `identify()` race fixed.** The pre-existing pattern
read identity inside a Task, racing concurrent identify Tasks.
Now reads identity synchronously on the caller's thread before
spawning the enqueue task. Deterministic ordering between
identify and a subsequent track.
- **Empty key validation on super-properties.** `register("", v)`
and `registerOnce("", v)` previously wrote a null-key entry
that landed on every wire event; now silently rejected at the
boundary.
- **Errors-consent gate.** The error pipeline now honours
`consent.errors` — previously only the analytics pipeline
observed consent. Consumers can independently allow analytics
while denying error capture (or vice versa).
- Removed dead code: stale `(anon, cust)` tuple in error capture
+ unused NSRange in `scrubPII`.
- **+19 new tests** (53 → 72 total). Coverage added for: sync
paywall reads from any thread, identify cache clearing under
same-id idempotent calls, identify cache clearing across
customer switches, `stop()` rejecting subsequent calls
(idempotent stop), URL-stub HTTP tests covering 2xx success,
4xx permanent (400/401/422), 5xx retryable, 408 retryable,
429 + Retry-After honoured, Idempotency-Key shipped verbatim
in the request header, User-Agent header carries SDK name +
version.
- Public API is additive — every v1.0.0 caller still compiles.
- The `Crossdeck` class remains `@unchecked Sendable` with a
detailed safety comment explaining the lock pattern.