Prove it yourself — the adversarial verification guide
We don't ask you to trust our numbers. We hand you the test. For every guarantee Crossdeck runs live, this page gives you the precise binary invariant, the exact surface where you verify it, the operation that triggers it, and — the part that earns a paying integration — the adversarial cases we'd run if we were trying to break it ourselves, plus the explicit boundary where each guarantee stops. A guarantee that names its own edge is worth more than one that claims perfection. The model is matched or held, never guessed: when Crossdeck can't be sure, it holds and tells you, it doesn't paper over.
TL;DR
- Every guarantee below is live — it runs against real data, not a slide. Contracts that aren't live yet aren't on this page.
- Each one names where you verify it: your own browser console (the SDK self-checks, live, in the moment) or the per-project contract log in Settings (server-side checks, with history). Look in the wrong place and a real guarantee looks unverifiable — so we signpost every one.
- Each entry hands you adversarial cases: concurrent operations, anonymous-then-identified flows, races, identity changes mid-session. Run them. They're how you falsify a claim, and we list them because they hold.
- Each entry states the boundary — what the guarantee does not cover. That's not a hedge; it's the difference between a contract and a marketing line.
The two surfaces
There are two places a guarantee is verified, and no single place shows everything. Knowing which is which is the whole game — most "I can't verify this" confusion is just looking at the wrong surface.
- Your browser console — live. The seven SDK-integrity contracts run inside your app on every Crossdeck call. Enable verification per app in the Apps tab, open your browser console, and act — they fire in real time. Passes aren't stored (ephemeral by design); failures report to Crossdeck's central reliability channel so we harden the SDK for everyone (a PII-free, independent-controller channel — see Privacy Policy §6). So you verify these in the moment, not from a stored per-project record — there isn't one, by design, and we won't fabricate one.
- The contract log — the record. The data-fidelity contracts run server-side against your project's own records and write every result — pass or fail, timestamped — to the log in Settings → Contract Test. The live console proves it works now; the log proves it's been working consistently. Failures stay visible; a red row that later recovers is itself the proof the test catches real things.
How to read an entry
Every entry has the same seven parts, so you can scan straight to the one you care about:
- Guarantee — the promise, in plain English.
- Invariant — the precise binary rule the verifier checks. True or false, no shades.
- Where to verify — browser console, or the log. The surface.
- How to test — the operation that triggers a verification you can watch.
- Try to break it — adversarial cases. Run these to try to falsify the claim.
- The boundary — where the guarantee stops and what we explicitly don't claim.
- What you'll see — the pass result on its surface, or the honest held/degraded state.
Verified in the log server-side · per project
These run server-side against your project's own records and write a timestamped pass/fail row to Settings → Contract Test every time. Do the action, refresh, and watch the result land. The verifier is read-only and re-derives its expectation independently of the code that wrote the data — so a green is a real second opinion, not the writer grading its own work.
Identity stitch
identity identity-stitch- Guarantee
- When two records turn out to be the same person and you merge them, their subscriptions, revenue, and entitlements collapse into one. Nobody pays twice, nobody loses access, nothing is counted twice.
- Invariant
- After a merge: exactly one survivor holds the union; the loser is archived and points to the survivor; no subscription or purchase is orphaned; revenue is not double-counted; the survivor's entitlements cover every active subscription. All six hold, or it fails.
- Where to verify
- The contract log. Server-side, your project only.
- How to test
- Create the same person twice — an anonymous session, then sign in (or two browsers). Make a test purchase on one. Resolve the conflict in Settings → Conflicts, then Refresh the contract log.
- Try to break it
-
- Buy on both records before merging. Two live subscriptions for one human. After the merge, revenue must not double-count and the survivor's entitlements must still cover both subscriptions.
- Chain the merges: merge A→B, then B→C. A subscription minted on A must still resolve to C through the chain — not read as orphaned. The verifier follows the merge chain at read time.
- Race the repoint window. Fire an event on the loser record in the moment after you click merge but before the async repoint completes (its
resolvedCdcuststill points at the loser). This must read as mid-repoint / held, not as a red. A genuinely wrong attachment is red; a mid-flight one is held. - Comp the survivor a manual grant. Add an entitlement no subscription pays for. The contract must stay green — it asserts subscriptions are covered, not that entitlements are exactly the subscription set.
- The boundary
- It proves the mechanics of a merge are lossless and consistent — not that the human decision to merge was correct. "Are these two the same person?" is the operator's call, logged with a rationale. And it asserts the subset direction on entitlements (every active subscription is covered); it does not claim the survivor has only those entitlements, because comps, promos, and manual grants legitimately add more. We assert what's provably true and nothing wider.
- What you'll see
- A passing row with six green checks. A failure names the diverging check (e.g.
revenue_no_double_count). A merge caught mid-repoint reads as held, not failed.
Error context
errors error-context- Guarantee
- Every error attaches to the real person who hit it, positioned correctly in their journey — never a floating, unattributed exception.
- Invariant
- For each sampled error: the user resolves to a live customer (through any merge chain); the client timestamp is coherent with server receipt (client ≤ received + clock-skew allowance); a session is present.
- Where to verify
- The contract log. Server-side, your project only.
- How to test
- Trigger an error while signed in as a user. Open that user's timeline, then Refresh the contract log.
- Try to break it
-
- Throw before identifying. Error from an anonymous session, then sign in so the identity attaches afterward. The error must still resolve to the now-merged live customer through the chain, not read as orphaned.
- Spoof the client clock. Send an error stamped far in the future. Position uses the client timestamp (when it happened to the user), but it's bounded against server
receivedAt+ a clock-skew allowance — a wildly future client time fails coherence rather than silently reordering the journey. - Error mid-merge. The user resolves through the chain to
live | broken | pending; pending (mid-repoint) is held, not red. Only a genuinely unresolvable user isbroken(red). - Strip the session. An error with no session fails
session_present— a real red, as it should be.
- The boundary
- It proves the error is correctly attributed and positioned — not that your application's error message is right or its stack trace complete. Crossdeck verifies the plumbing (right person, right place, real session), not the semantics of what your code threw. Position is by the client clock; server receipt time is used only to bound clock skew and for dedup.
- What you'll see
- A passing row (
user_resolves_to_live_customer,position_timestamp_coherent,session_present). A failure names the diverging check.
Journey fidelity
analytics journey-fidelity- Guarantee
- The journey we show is the real path the user took — correctly ordered, no duplicates, with sessions that don't split when they navigate.
- Invariant
- Within a sampled customer's stream: no duplicate event ids; each session carries a single identity; that identity resolves live; gaps within a session stay inside the 30-minute window; session boundaries are ordered.
- Where to verify
- The contract log. Server-side, your project only.
- How to test
- Click through several pages across a navigation. View that visitor's journey, then Refresh the contract log.
- Try to break it
-
- Replay an event id. Fire the same event twice (a network retry or a double-fire). The duplicate must be collapsed — a repeated id fails
no_duplicate_event_ids. - Switch identity mid-session. Sign in halfway through a session. The session must resolve to one identity through the merge chain (the survivor), not read as two — that's
session_single_identity. - Idle past the window. Go quiet for 31 minutes, then act. That must open a new session, not stretch one across the gap.
- Reorder arrival. Make events land at the server out of order. Ordering is by the client timestamp (when it happened), so boundary ordering still holds regardless of arrival order.
- Replay an event id. Fire the same event twice (a network retry or a double-fire). The duplicate must be collapsed — a repeated id fails
- The boundary
- It proves ordering, dedup, session integrity, and identity — it does not claim "no dropped events." Proving nothing was lost needs a client-side sequence number the event stream doesn't carry, so we don't assert it. We'd rather name the gap than imply a guarantee we can't verify.
- What you'll see
- A passing row across the five checks. A failure names which one diverged.
Proven in CI build time · every release
This one isn't toggle-and-watch, and we won't pretend it is. It's proven by a ground-truth fixture test in our CI on every release, because a live re-derive would be tautological — the same event stream computing the number and then checking itself. The fixture hand-specifies the inputs and asserts the exact output, so the arithmetic is pinned against a known-correct answer a human wrote.
Page CTR
analytics page-ctr- Guarantee
- Click-through rate is computed honestly — unique clickers ÷ unique visitors, every click attributed to the page it actually happened on.
- Invariant
- CTR(page, action) = |unique customers who clicked that action on that page| ÷ |unique visitors to that page|. Unique over unique; clicks attributed to their own page.
- Where to verify
- CI, every release — a fixture test against the pure
computePageJourneycore. - How to test
- Read the fixture and run the backend suite. The fixture specifies visitors and clicks and asserts the exact CTR — change the computation and the test goes red.
- Try to break it
-
- Double-click. The same visitor clicks the same action twice — counted once (unique clickers, not click count).
- Click with no own-page. A click event that doesn't carry its own page is attributed to the visitor's current page, not dropped and not double-counted.
- The window-shopper. A visitor who never clicks lands in the denominator (a visitor), never the numerator.
- The boundary
- It proves the arithmetic is honest — unique-over-unique, correct attribution — not that a high CTR means a good page. And it's CI-proven, not user-toggleable, because there's no independent live ground truth to re-derive against; we say that plainly rather than dress it up as a live check.
- What you'll see
- The CI run, green on every release. The fixture is the ground truth; the test fails the day the computation drifts.
Verified in your console client-side · live
These run inside your app on every Crossdeck call. Enable verification per app in the Apps tab, open your browser console, and act — they fire in real time. There's no stored per-project history here by design: passes are ephemeral, failures go to our central reliability channel so we harden the SDK for everyone. You verify these in the moment, watching them happen.
Per-user cache isolation
entitlements per-user-cache-isolation- Guarantee
- Switching users never leaks the previous user's entitlements.
- Invariant
identify(B)makes user A's cached entitlement slot physically unreachable; noisEntitled()after the switch reads A's cache.- Where to verify
- Your browser console, live.
- How to test
identify("A"), read entitlements;identify("B"); read again. Watch the cache key rotate in the console; A's grants are gone.- Try to break it
-
- Ping-pong:
identify(A) → identify(B) → identify(A)rapidly. The slot is keyed by identity; each switch evicts, so A-again is a fresh read, never B's residue. - Race the switch: call
isEntitled()during theidentify()transition. It reads the new identity's slot or misses — never the old user's grants. - Log out and back in:
identify(null)then back to A. The anonymous slot can't serve A's prior cache.
- Ping-pong:
- The boundary
- It isolates the client cache; the server is always the source of truth. It doesn't claim the network is instantaneous — it claims you never read a stale other user's grants from cache.
- What you'll see
- The console logs the cache key rotating on each
identify(); the verifier fires green on every switch.
Idempotency-key determinism
revenue idempotency-key-deterministic- Guarantee
- Retrying a purchase sync never double-charges or double-grants.
- Invariant
- The idempotency key is a pure function of the receipt/input — same input derives an identical key, every SDK, every retry.
- Where to verify
- Your browser console, live.
- How to test
- Call
syncPurchases(receipt)twice with the same receipt. Watch the derived key match in the console. - Try to break it
-
- Retry after a failure: kill the network mid-sync, then retry. The key is identical, so the backend collapses it to one effect.
- Fire concurrently: two syncs of the same receipt at once. Same key → the server dedups.
- Two devices, one receipt: the key is derived from the receipt, not device state, so it matches across installs.
- The boundary
- It guarantees the key is deterministic, so the server can dedup; the dedup itself is the server's job (and is contracted server-side). It doesn't stop the network from retrying — it makes retries safe.
- What you'll see
- The console logs the derived key; identical across calls with the same receipt.
Error-envelope shape
errors error-envelope-shape- Guarantee
- Every API error comes back in one predictable shape, carrying a request id you can quote to support for an end-to-end trace.
- Invariant
- Every error response parses to a fixed envelope —
code,message,requestId— with the request id always present. No untyped throws escape. - Where to verify
- Your browser console, live.
- How to test
- Trigger an API error — a bad key works. Watch the SDK parse the envelope in the console.
- Try to break it
-
- Walk the codes: provoke a 401, a 429, a 500. Each yields the same envelope shape with a request id.
- Malform the body / timeout: a garbled response or a network timeout surfaces as a typed network error, never a raw exception you have to guess at.
- The boundary
- It locks the shape and the presence of a traceable request id — not whose fault the error is. The request id is what lets support trace one failure end-to-end.
- What you'll see
- The console logs the parsed envelope with a
requestIdyou can quote to support.
Flush-interval parity
diagnostics flush-interval-parity- Guarantee
- Events flush on the same 2-second cadence across every SDK — no platform silently buffers longer.
- Invariant
- The live flush interval equals the code-pinned constant on boot, identical across SDKs, unless you explicitly override it.
- Where to verify
- Your browser console, live (the boot self-test).
- How to test
- Start the SDK. The boot self-test reports the live interval in the console.
- Try to break it
-
- Background the tab / throttle timers: the verifier reads the configured interval the loop actually runs on, not a documented number.
- Override it: set a custom flush interval — the self-test reports your value, proving it reads reality, not a constant.
- The boundary
- It pins the default cadence parity across SDKs; you can override it deliberately. It doesn't promise delivery latency — it promises the flush loop runs on the interval it reports.
- What you'll see
- A boot console line with the live interval (the pinned default, or your override).
Super-property precedence
analytics super-property-merge-precedence- Guarantee
- Your registered properties always win over auto-collected device fields — no silent clobbering of the values you set.
- Invariant
- On a key collision, the
register()'d value takes precedence over the auto-collected one. Precedence order is fixed. - Where to verify
- Your browser console, live.
- How to test
register()a super-property, thentrack()an event with a colliding key. Watch your value win in the console.- Try to break it
-
- Collide with an auto key (e.g. a device field): your explicit value still wins.
- Re-register: register, then register the same key again — last explicit write wins, deterministically.
- Register null: an explicit null is still an explicit value, applied as such, not silently dropped to the auto value.
- The boundary
- It guarantees precedence — your explicit values win — not the meaning of the value. It orders the merge; it doesn't validate your data.
- What you'll see
- The console shows the merged property set with your registered value present.
Payload schema-lock
diagnostics contract-failed-payload-schema-lock- Guarantee
- Our own failure telemetry carries only diagnostic fields — never your users' personal data. The wire shape is locked.
- Invariant
- The
crossdeck.contract_failedpayload's field set ⊆ the lockedallowedFields; nocdcust, no email, no PII;sdk_platformenum-locked to the known SDKs. - Where to verify
- Your browser console, live (self-checks on every report).
- How to test
- Watch the payload's field set in the console on any
contract_failedreport. No personal data rides along. - Try to break it
-
- Force a failure with user context in scope: the payload still contains only the allowed diagnostic fields — the lock forbids the rest by construction.
- Try to attach a custom field: it doesn't reach the wire; the field set is fixed.
- The boundary
- It locks the wire shape of our reliability telemetry — the independent-controller channel that carries SDK failures from your app to us — keeping it PII-free. It's the structural backing for the lawful-basis analysis in Privacy Policy §6. Adding a field requires a pull request to this contract and a privacy amendment.
- What you'll see
- The console shows the payload — only the allowed diagnostic fields, never user data.
Error-codes catalogue
errors sdk-error-codes-catalogue- Guarantee
- Every error code the backend can return has a plain-English meaning and a concrete fix, right in the SDK.
- Invariant
- Every backend error code resolves to a catalogue entry with remediation text. No orphan codes.
- Where to verify
- Your browser console, live (the boot self-test).
- How to test
- The boot self-test confirms every backend code resolves to remediation. Watch the count in the console.
- Try to break it
-
- Receive an unknown code: a code with no catalogue entry is the failure — the self-test flags the gap rather than passing silently.
- Receive a deprecated code: still resolves to an entry; nothing falls off the back.
- The boundary
- It guarantees coverage and that remediation text exists — not that the remediation fixes your specific bug. Every code is documented; the fix is guidance, not a promise about your code path. See the canonical catalogue.
- What you'll see
- A boot console line confirming every code resolves (N of N).
What we deliberately don't claim
A guarantee is only as trustworthy as its stated edge. So, plainly, here is what these contracts do not assert — by design, because we can't prove it, or because it isn't ours to prove:
- "No events were dropped." Journey fidelity proves ordering, dedup, and session integrity — not completeness. That needs a client sequence number the stream doesn't carry. We don't claim it.
- "The merge decision was correct." Identity stitch proves a merge is lossless and consistent — not that two records are the same human. That's the operator's call, logged with a rationale.
- "A high CTR means a good page." Page CTR proves the arithmetic is honest, not that the number is flattering.
- AI summaries are present and attached, not correct. Where Crossdeck attaches an AI summary, the contract verifies it's wired and present — never that it explains the error correctly.
- The remediation text fixes your bug. The error-codes catalogue guarantees coverage and guidance, not a solution to your specific failure.
Matched or held, never guessed. When a check can't be made with certainty, the verifier holds and tells you — it doesn't fabricate a green. That's the whole point: a record you can trust because it admits its own edges.
Related
- The contract ledger — every structural guarantee, with the source files that implement it and the tests that fail the day we break it. This page is the adversarial companion to that ledger.
- Identity verification — the identity journal and resolver order that identity-stitch, error-context, and journey-fidelity all depend on.
- Entitlements & gating — how entitlements are defined and answered, behind per-user-cache-isolation.
- Privacy Policy §6 — the independent-controller channel that payload-schema-lock keeps PII-free.
Updated May 31, 2026. Every guarantee on this page runs live at HEAD of the monorepo's main branch. Contracts that aren't live yet aren't listed — we teach only what you can verify today.