# Identity Explorer

> Source: https://cross-deck.com/docs/workbench/identity-explorer/ · Crossdeck developer docs (generated from the published page).

Workbench
7 min read
·
One person, one canonical customer — proven, not assumed

A single person arrives at your product as many fragments: an anonymous device id minted before they ever sign in, the user id you assign when they do, a Stripe or Apple customer the moment they pay. Crossdeck&rsquo;s job is to collapse those fragments into one canonical customer so you count one person once. The Identity Explorer is how you *see* that collapse — and how you *test* it. It does not hold its own opinion of who someone is; it reuses the exact resolver every event runs through and round-trips every fragment back through it. A fragment that doesn&rsquo;t resolve home is a split, shown mechanically.

## TL;DR

- **Give it any one fragment** — a customer id, an anonymous id, a developer user id, an email, or a rail id — and it resolves the canonical person and lays out their whole identity graph.

- **Every axis is round-tripped** through the same resolver every event uses. An axis is `linked` (resolves home), `drifted` (resolves to a different customer), or `unlinked` (no index entry — an orphan fragment).

- **A person is `consistent` only when every axis resolves home.** That is the mechanical &ldquo;one person, one dot&rdquo; test — run by the production code, not a heuristic.

- **It surfaces the enforced `identity-stitch` contract&rsquo;s latest verdict** — the same contract test the platform runs to prove merges never orphan money or access.

- **Read-only.** It never merges, repoints an index, or runs the contract.

## The fragments of a person

Crossdeck stores one person as a single canonical customer record (a `cdcust_…`) that absorbs every identity fragment it has seen for them. Those fragments are the axes the Identity Explorer walks:

Axis Where it comes from

Anonymous device The id the SDK mints before sign-in. A person can carry several over time; they all live on the one customer.
Developer user id The id you pass to identify() when the person signs in.
Stripe customer The cus_… attached when they pay through Stripe.
Apple transaction The original transaction id from an App Store purchase.
Apple appAccountToken The install-stable token the Swift SDK stamps on a purchase — its own identity channel, independent of who owns checkout.
Google purchase The purchase token from a Play Store purchase.

Each axis also has an *index entry* — a small pointer (`anon:…`, `developer:…`, `stripe:…`) that maps that fragment back to its canonical customer. The index is what makes resolution O(1) on every event. The Identity Explorer&rsquo;s test is, in effect, &ldquo;does every pointer still point home?&rdquo;

## The round-trip test

This is the heart of the tool, and why it is trustworthy. For each axis the canonical person holds, the Explorer feeds that axis back into `resolveCrossdeckCustomerId` — the identical function the event pipeline calls to decide &ldquo;whose event is this?&rdquo; — and follows the merge chain to the live customer. The answer is compared to the canonical person.

State Meaning

linked The axis resolves home to this canonical customer. The stitch holds.
drifted The axis resolves, but to a different customer — its index pointer points away from this person. A drift the reconciler should re-home.
unlinked The axis has no index entry at all — an orphan fragment. This is how &ldquo;two dots for one person&rdquo; appears at the index layer.

A person is **consistent** when every axis is `linked`. Because the test runs the production resolver rather than re-implementing the logic, a green verdict here means the same thing a green verdict means in production: every fragment of this person funnels to one customer, so they count once and see one coherent history.

Why round-trip instead of just reading the record?

Reading the customer doc tells you what Crossdeck *believes*. Round-tripping each axis tests whether the *index* agrees — whether a later event arriving with that fragment would actually land on this person. The two can disagree after an interrupted merge or a drifted pointer, and that disagreement is exactly the bug you came to find.

## Running a lookup

Open **Developers → Workbench → Identity Explorer**. Pick the identifier type (or leave it on *Auto-detect*), paste the value, and run it:

- **Auto-detect** reads the shape — `cdcust_…`, `anon_…`, `cus_…`, an email — and falls back to a forgiving cascade across the other axes for anything else, so a bare id still finds its person.

- **Explicit types** — Customer id, Anonymous id, User id, Email, Stripe customer, Apple transaction, Google purchase — when you want to be precise.

A value that resolves to nothing returns a plain *Not found* — a fact, not an error. An anonymous id that resolves to nothing is itself the finding: an unstitched fragment with no customer behind it.

## Reading the result

The result reads top to bottom:

- **Verdict** — *Consistent* (every axis resolves home) or *Split* (one or more do not), stated plainly.

- **Canonical customer** — the `cdcust_…` the input resolved to, with environment, developer user id, email, display name, and how many anonymous devices it carries.

- **Identity axes** — every fragment, its index key, where it resolves, and its `linked` / `drifted` / `unlinked` state.

- **Merge history** — earlier customer records that were merged into this person. Their ids resolve here through the merge chain, which is why an old id still finds the right customer.

- **Contract** — the enforced `identity-stitch` verdict (below).

- **Notes** — plain-English observations, including the precise reason for any split.

## The enforced contract

Crossdeck runs an enforced contract test, `identity-stitch`, that re-derives every customer merge from source records and checks six invariants — the survivor is identified, every loser points to the survivor, no subscription or purchase is left orphaned, revenue is never double-counted, and the survivor&rsquo;s entitlements cover its subscriptions. The Identity Explorer surfaces that contract&rsquo;s most recent recorded verdict for your project, so the per-person view sits next to the project-wide guarantee.

It reads the verdict; it does not run the contract.

Running the contract writes a run record — a mutation. To stay strictly read-only, the Explorer reads the latest verdict already on file rather than triggering a fresh evaluation. The verdict you see is the platform&rsquo;s own last word, unedited.

## Where &ldquo;two dots&rdquo; come from

The most common split has a simple cause, and the Explorer makes it legible. On mobile, the Swift SDK mints one anonymous id at first launch and keeps it for the life of the install — one device, one id. On the web, the anonymous id lives in per-origin storage, so a person who onboards on your marketing origin and lands in the app on a different origin re-mints a fresh id. Until an `identify()` stitches the two, that one person exists as two anonymous fragments — &ldquo;two dots.&rdquo; In the Explorer, the second fragment shows as `unlinked` (no customer yet) or, once stitched, as a second `linked` anonymous axis on the same canonical customer.

The fix is identify, not the Explorer.

The Explorer reveals a split; it never repairs one (it is read-only). The repair is the normal stitch path: when the person signs in and the SDK calls `identify()`, Crossdeck attaches the new fragment to the existing customer. See [Identify users](https://cross-deck.com/docs/identify-users/index.html).

## Read-only

- **Reads only.** The tool reads customer records, index pointers, and the contract log. It never merges customers, repoints an index, or writes a contract run. The guarantee is pinned by a test that scans the tool&rsquo;s source for any write API and for any call that would run the contract.

- **No verdicts beyond fact.** `linked` / `drifted` / `unlinked` are mechanical outcomes of the production resolver, not opinions. The tool reports what resolution does; the conclusion is yours.

- **Owner / member gated.** Like every Workbench tool, it is an operator surface and never exposed to your customers.

## Related

- [Workbench](https://cross-deck.com/docs/workbench/index.html) — the hub the Identity Explorer lives in, and the read-only principles it inherits.

- [Rail Ledger](https://cross-deck.com/docs/workbench/rail-ledger/index.html) — the sibling tool: the rail&rsquo;s truth for a transaction, next to your stored record.

- [Identify users](https://cross-deck.com/docs/identify-users/index.html) — the stitch path that links an anonymous fragment to a known person, and the repair for a split.

- [Identity verification](https://cross-deck.com/docs/identity-verification/index.html) — how Crossdeck proves an identity claim before acting on it.
