Crossdeck Docs
Dashboard

Contracts — the structural guarantees Crossdeck enforces about itself

Architecture Audience: senior engineer / CTO / procurement counsel · ~22 min read · Updated May 27, 2026

Putting Crossdeck behind your paywall means trusting a third party with the question that pays your invoices: is this customer entitled to what they paid for, and will the next release of the SDK still answer that question the same way? A contract is the platform's structural answer. Each one is a single sentence we promise to keep true about the runtime — per-user cache isolation, cross-SDK idempotency-key determinism, error-envelope shape, lifecycle teardown completeness, payload schema-locks — paired with the source files that implement it and the tests that prove it on every release. This page is the full ledger. It is the evidence list a CTO or procurement counsel asks for before a paying app trusts us with its revenue, written so you can read every claim, click through to the code, and click through to the test that fails the day we break it.

TL;DR

If you read nothing else.

Crossdeck publishes its structural guarantees as machine-readable JSON contracts under contracts/. Each contract is one claim about the runtime, the source paths that implement it, and the test paths that prove it. Per-SDK assertion tests run on every release and a CI workflow fails the build if a contract's tests are missing or the test names drift. The CrossdeckContracts registry is bundled inside every SDK — your own application can read the same ledger your auditor reads. Twenty contracts are enforced today across seven pillars; one is registered as proposed ahead of the feature shipping. The full table is at § ledger.

The trinity — claim, code, test

Most platform documentation describes runtime behaviour in prose and asks the reader to trust that the runtime matches. That is the wrong order. The product of a contract is not the prose; it is the matched triple of a claim that's small enough to be true, source code that implements it, and a test that fails the day the source drifts. Crossdeck's contracts are stored as JSON files where each carries exactly those three fields — plus pillar, status, and the SDK platforms it applies to.

The schema is fixed at contracts/:

{
  "id": "per-user-cache-isolation",
  "pillar": "entitlements",
  "status": "enforced",
  "claim": "Every identify(userId) switches the entitlement cache to a physically separate per-user storage slot — `crossdeck:entitlements:<sha256(userId)>` — and unconditionally wipes the in-memory snapshot. A user-switch on a shared device CANNOT cross-read a prior user's cached entitlements, even if the in-memory clear is somehow skipped, because the storage keys are physically separate. reset() wipes every per-user slot via the persisted index.",
  "appliesTo": ["web", "react-native", "swift", "android"],
  "codeRef": [
    "sdks/web/src/entitlement-cache.ts",
    "sdks/swift/Sources/Crossdeck/EntitlementCache.swift",
    "..."
  ],
  "testRef": [
    {
      "file": "sdks/web/tests/entitlement-cache-isolation.test.ts",
      "name": "identify(B) makes A's entitlements unreachable from in-memory"
    },
    {
      "file": "sdks/swift/Tests/CrossdeckTests/EntitlementCacheIsolationTests.swift",
      "name": "test_identifyB_makesAEntitlementsUnreachable"
    }
  ],
  "registeredAt": "2026-05-26",
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0"
}

Every row in the ledger below is one of these JSON files, rendered. The links in the Code column point at the source files; the links in the Tests column point at the test files plus the exact test name. The reader is invited to open both columns in parallel, see that the claim, the source, and the test agree, and walk away with primary-evidence confidence — not faith.

The pillars

The contract set is organised into seven pillars, each grouping structural guarantees that share a runtime concern.

PillarWhat it guardsContracts
Entitlements The hot-path read every paywall depends on. Isolation between users, persistence across reloads, behaviour under outage. 1 enforced
Revenue The money paths — purchases sync, idempotency, StoreKit transaction finishing, App Store account-token shape. 4 enforced
Errors The wire vocabulary every SDK speaks back to the customer when something goes wrong — same envelope shape, same error codes, same human-readable resolutions. 3 enforced
Lifecycle The runtime invariants of start / reset / stop / shutdown. Durable persistence across teardown, no observer leaks, no events lost when a process is going away. 4 enforced
Analytics Cross-platform parity of the event pipeline. Same flush cadence, same property-merge precedence, same PII scrub posture, same funnel anchors. 5 enforced
Webhooks HMAC verification, mandatory replay windows, distinguishable failure codes. Plus documentation honesty — what we ship today versus what is on the roadmap. 2 enforced · 1 proposed
Diagnostics The shape of our own operational telemetry — schema-locked payloads, independent-controller flow, no end-user data on the wire. 1 enforced

The status lifecycle

Contracts move through three states. The status is not decoration — it is what we are committing to.

StatusWhat it meansWhat we owe you
enforced The claim is true at HEAD. Test references are real tests in CI; the contract-audit workflow blocks a merge if any test reference cannot be matched to a real test name in the repository. If a contract is enforced, our CI cannot land a green merge that breaks it. A new SDK release ships only when every enforced contract's tests pass on every applicable platform.
proposed The wire shape is locked before the feature ships. Used for forward-looking commitments — e.g. outbound webhook delivery — where we want the schema, retry policy, and event vocabulary fixed in the repository before any code arrives. If a contract is proposed, the eventual enforced implementation will honour the registered shape. The customer-facing documentation says roadmap until the status flips.
retired Kept for historical reasoning — the claim was once enforced and the platform no longer needs it (e.g. an SDK surface was removed). Retired contracts stay in the registry so old release notes resolve. A retired contract makes no current promise. The registry's retired rows are out of scope for the CI gate.

How enforcement works

The contract set is enforced at four layers. Each layer fails a different kind of drift.

1. The schema-lock JSON

Every contract is a JSON file under contracts/<pillar>/<id>.json. A pre-commit and CI validator checks the shape: required fields present, id unique, status in the allowed set, appliesTo in the SDK enum, codeRef and testRef non-empty for any enforced contract. A pull request that mis-shapes a contract is blocked before review.

2. Per-SDK assertion tests

Every testRef entry must resolve to a real test in the repository. For TypeScript SDKs (Web / Node / React Native) the test name is matched against the Vitest test catalogue; for Swift the XCTest method name is matched; for Android the JUnit test method or backtick-quoted name is matched. The contract-audit GitHub Actions workflow fails the build if any reference does not resolve, or if a test name has drifted (renamed, removed, etc.) without an accompanying contract update.

3. Bundled runtime registry

Each SDK ships with the full contract set as a JSON resource inside the artefact and exposes it via CrossdeckContracts:

// Web / Node / React Native
import { CrossdeckContracts } from "@cross-deck/web";

for (const contract of CrossdeckContracts.all()) {
  if (contract.status !== "enforced") continue;
  console.log(`[crossdeck] ${contract.id} (${contract.pillar})`);
}

const isolation = CrossdeckContracts.byId("per-user-cache-isolation");
if (!isolation || isolation.status !== "enforced") {
  throw new Error("entitlement-cache isolation contract is not enforced");
}
// Swift
import Crossdeck

for contract in CrossdeckContracts.all() where contract.status == .enforced {
    print("[crossdeck] \(contract.id) (\(contract.pillar.rawValue))")
}

guard let isolation = CrossdeckContracts.byId("per-user-cache-isolation"),
      isolation.status == .enforced else {
    fatalError("entitlement-cache isolation contract is not enforced")
}
// Android
import com.crossdeck.CrossdeckContracts

for (contract in CrossdeckContracts.all()) {
    if (contract.status != ContractStatus.ENFORCED) continue
    println("[crossdeck] ${contract.id} (${contract.pillar.wire})")
}

val isolation = CrossdeckContracts.byId("per-user-cache-isolation")
check(isolation?.status == ContractStatus.ENFORCED) { "isolation contract is not enforced" }

The same generation script (scripts/emit-contracts.mjs) writes the bundle for every SDK from the canonical JSON, with the SDK's published version stamped into the bundle's bundledIn field. Drift between what we publish on the website and what your SDK believes is impossible by construction.

4. Customer-facing failure signal

When an enforced contract test fails — in our CI, in a dogfood run, or in a customer's own contract-verification harness — the SDK exposes a one-call helper to report the failure:

cd.reportContractFailure({
  contractId: "per-user-cache-isolation",
  failureReason: "expected isolation across user switch, got cross-read",
  runContext: process.env.CI ? "ci" : "dogfood",
  runId: process.env.GITHUB_RUN_ID ?? crypto.randomUUID(),
  testRef: {
    file: "tests/entitlement-cache-isolation.test.ts",
    name: "identify(B) makes A's entitlements unreachable from in-memory",
  },
});

This call fires the crossdeck.contract_failed event on a dedicated reliability channel — never the customer's own appId. The privacy framing is detailed at § privacy; the wire shape is itself schema-locked by the contract-failed-payload-schema-lock entry below.

The failure-mode promise

A contract is only as useful as what happens the day it fires. Three environments, three different promises.

EnvironmentWhat happens when an enforced contract fails
Our CI The merge is blocked. Every PR runs the per-SDK test suite plus the contract-audit workflow. A failing enforced contract's test cannot reach main; the SDK release pipeline reads from main exclusively.
Your CI Your test fails through your own reporter (Vitest, XCTest, JUnit, Jest) — same way any failing test in your suite fails. The optional reportContractFailure(…) hook additionally fires crossdeck.contract_failed to our reliability endpoint, so we can spot a contract regression in your pinned SDK version across every customer running our SDK at the same release. Your dashboard never sees this event; it lands only in Crossdeck's operations workspace.
Your production app SDK code paths that depend on a contract degrade tolerably rather than crash — isEntitled returns false if the cache is unreachable, track() persists to the durable queue if the network is offline, syncPurchases retries with the bounded exponential backoff documented in the revenue pillar. The runtime never throws an uncaught exception into your app because of an internal SDK contract violation; failures are surfaced as typed CrossdeckError values you can log or ignore.

Runtime self-verification (v1.5.1+, Web SDK)

The contracts in the ledger above are tested in Crossdeck's CI on every release. The CI tests are necessary but not sufficient — they verify the structural guarantee held the moment we packaged the artefact, but they cannot verify the same guarantee continues to hold when a real customer cold-starts the SDK against a real browser's IndexedDB, a real device's network conditions, a real production identity-rotation sequence. The runtime self-verification layer closes that gap. Every Crossdeck SDK install actively tests its own contracts as it operates and reports failures back to Crossdeck's reliability workspace before the customer's own dashboard would have noticed. Available in the Web SDK at v1.5.1; ports to Node, React Native, Swift, and Android in the next patch release of each.

The platform-hardening signal.

Every install in the field tests its own structural contracts as it operates. PASS results stream to the customer's engineer's devtools console (when enabled — defaults dev=on, prod=off). FAIL results stream silently to Crossdeck's reliability workspace via a dedicated one-way channel that never touches the customer's own appId or dashboard. The Crossdeck operations team sees a contract regression in a customer's pinned SDK version on the day it first fires, not on the day the customer notices their paywall is wrong.

The two layers

LayerWhen it runsWhat it does
Boot self-test Once, on Crossdeck.init(...). Runs every applicable verifier against an isolated test context — the customer's real SDK state is never mutated. Prints a pass/fail summary to the console. Useful as the engineer-time proof that the contracts the SDK ships are honoured at runtime, not just in our CI.
Hot-path verifiers Continuously, on every relevant SDK operation: every identify(), every syncPurchases(), every error-envelope parse, every track(). Observes the operation the SDK just completed and asserts the contract claim held — that identify(B) actually rotated the entitlement-cache slot away from A, that the idempotency key derived for an Apple JWS matches the canonical cross-SDK vector, that the error envelope had the four required fields. PASS results emit at DEBUG level (cosmetic). FAIL results emit at WARN AND fire reportContractFailure(...) to the reliability channel.

The boot self-test is the loud, intentional verification a developer turns on during integration testing. Hot-path verifiers are the silent, always-on watchdogs that catch the real-world failures the boot test cannot — the network blip, the storage-quota exhaustion, the runtime that surprised us. Both layers report to the same reliability channel on failure; both honour the same three switches described below.

Reading the console output

When logVerifierResults is enabled (default in development), the customer's devtools console streams the verifier results in real time. Boot self-test renders as a header + indented rows + summary; hot-path verifiers render one line per operation.

// Boot self-test — prints once on Crossdeck.init()
[crossdeck] Contract self-verification — running 5 tests
   ✓ per-user-cache-isolation — slot rotated A:7c44…ee20 → B:a3f2…01b9 (isolated, physically separate) (3ms)
   ✓ idempotency-key-deterministic — apple JWS → a66b1640-efaf-bb4d-1261-6650033bf111 (canonical vector + determinism + rail isolation) (1ms)
   ✓ error-envelope-shape — { type, code, message, request_id } parsed and type ∈ ApiErrorType (0ms)
   ✓ flush-interval-parity — eventFlushIntervalMs = 2000ms (canonical default) (0ms)
   ✓ super-property-merge-precedence — caller > super > device verified (synthetic merge) (1ms)
[crossdeck] Self-verification passed — 5 passed, 0 failed (8ms)

// Hot-path verifiers — print as the SDK exercises each contract
[crossdeck.identify]     ✓ per-user-cache-isolation — slot rotated _anon → a3f2…01b9
[crossdeck.track]        ✓ super-property-merge-precedence — caller(2) > super(1) > device verified
[crossdeck.syncPurchases] ✓ idempotency-key-deterministic — apple → a66b1640-efaf-bb4d-1261-6650033bf111
[crossdeck.errorParse]   ✓ error-envelope-shape — invalid_request_error/missing_customer on 400

FAIL lines use the same shape with instead of and the failureReason in place of the evidence string:

[crossdeck.identify] ✗ per-user-cache-isolation — in-memory snapshot still held entitlements after slot rotation (2ms)

When you see a FAIL line, three things have happened: the contract was violated for real (this is not a flake), Crossdeck's reliability workspace has just received a crossdeck.contract_failed event with the same contract_id + failure_reason, and Crossdeck's operations team will see it in their reliability dashboard the moment the event lands. Action on your side: open an issue, capture the conditions that produced it (browser / OS / SDK version), and reach out at [email protected]. Crossdeck will be working on it from the moment the event arrives regardless.

The three switches

Three independent flags on CrossdeckOptions govern the layer. Each docstring in the source explicitly names the other two as the wrong tool for the wrong job so no engineer can accidentally conflate them.

FlagDefaultWhat it controls
verifyContractsAtBoot Development: true
Production: false
Whether the boot self-test runs on Crossdeck.init(...). Hot-path verifiers are unaffected. Opt-in for production by setting explicitly.
logVerifierResults Development: true
Production: false
Whether PASS results print to the console. Cosmetic only. FAIL results always print at WARN regardless of this flag. Failure reporting to Crossdeck's reliability channel is also unaffected.
disableContractAssertions Always false Kill-switch — disables the entire layer. No console output, no telemetry, no reliability-channel writes. The sovereignty escape hatch for enterprise customers whose posture forbids any outbound diagnostic telemetry to third-party controllers. See § sovereignty.

The three flags resolve with a strict precedence:

code option (Crossdeck.init({...}))
     > dashboard remote config (per-app, see § dashboard)
     > DEBUG/RELEASE default

Code always wins so engineers retain ultimate control. Dashboard is the no-deploy operational lever for QA / staging soaks. Default is what ships when nobody touches anything. Routing is the same for every flag:

  logVerifierResults: true logVerifierResults: false
PASS (boot)console.info ✓silent
PASS (hot_path)console.debug ✓silent
FAIL (any)console.warn ✗ + reliability channelconsole.warn ✗ + reliability channel

disableContractAssertions: true short-circuits the entire matrix above — every layer is silent end-to-end.

The dashboard toggle (per-app)

The Apps page in the dashboard (/dashboard/apps/) exposes the same two flags as a per-app remote config that the SDK fetches on boot via /v1/config. Operationally, your QA / staging team can flip Console output to Force on for a single staging app, refresh the page, and watch the verifier stream in their devtools console — without changing any code, without redeploying, without coordinating with the engineering team.

Each toggle is tri-state:

disableContractAssertions is intentionally not exposed in the dashboard. It's the sovereignty kill-switch (§ sovereignty below); it must live in customer source so procurement / audit teams can grep for it.

Dashboard changes propagate to a customer's running SDK on the next Crossdeck.init(...) call, modulated by a 60-second edge cache on /v1/config. Practically: open a fresh browser session, see the new behaviour. The customer's own engineering team retains code-level override at all times — a logVerifierResults: false in Crossdeck.init({...}) wins over a "Force on" in the dashboard.

The sovereignty kill-switch

Some enterprise customers operate under data-sovereignty postures that forbid any outbound diagnostic telemetry to third-party controllers, including Crossdeck. For these customers, disableContractAssertions: true in Crossdeck.init({...}) disables the entire verifier layer end-to-end: no verifiers run, no console output, no reportContractFailure writes, no /v1/sdk/diagnostic traffic.

This switch lives in customer source code by design, not in the dashboard. Procurement counsel and security audit teams who need to prove "no Crossdeck telemetry leaves our environment" can grep the customer's own repository for disableContractAssertions and read the exact code that disables the layer. A dashboard toggle for the same purpose would be unverifiable from outside the customer's source — the wrong tool for compliance evidence.

The runtime still behaves correctly with the kill-switch on.

Verifiers are observers, not assertions — the SDK's operational behaviour is unchanged when the layer is disabled. identify() still rotates the entitlement-cache slot, syncPurchases() still derives a deterministic idempotency key, track() still merges properties with the documented precedence. The only difference is that nobody is observing the operation and producing a result.

For your auditor

The runtime layer is a third source of audit evidence, alongside the CI test suite and the JSON contract registry described in § Verifying this independently. Specifically:

Reliability-channel privacy

The crossdeck.contract_failed event carries operational telemetry to Crossdeck's reliability team. It is the single piece of customer-side data Crossdeck processes as an independent controller, not as a processor on your behalf. The lawful basis is legitimate interest in running a reliable SDK; that basis depends on the payload being structurally incapable of carrying end-user data. The schema-lock contract contract-failed-payload-schema-lock enforces exactly that.

The full set of fields that may appear on the wire.

Required: contract_id, sdk_version, sdk_platform, failure_reason (categorical label, ≤128 chars), run_context (ci / dogfood / customer-app), run_id. Optional: test_file, test_name, device_class (categorical bucket like simulator / phone / desktop). Forbidden: anonymousId, developerUserId, crossdeckCustomerId, email, ip, user_agent, message, stack, stack_trace, frames, exception_message, url, path, screen, title, label, text, ariaLabel, accessibilityLabel, contentDescription, session_id, sessionId. The SDK strips forbidden keys before serialisation; the backend validator rejects them at the edge. See the schema-lock JSON.

Binary stability of the API

The CrossdeckContracts registry and its Contract shape are public, typed surfaces on every SDK. The stability promise:

The registered id of a contract is a stable handle. Once registered, it is never reused for a different claim. If a contract changes shape (e.g. tightens from "Web only" to "every SDK"), the existing id stays; the appliesTo set widens; the firstRegisteredIn stays as the original release; the test references widen.

The full contract ledger

Every contract in the registry, grouped by pillar. Each card shows the claim, the code paths that implement it, and the test paths that prove it. Links go to the file at the current main tip on GitHub.

Entitlements   Entitlements

The hot-path read every paywall depends on. One contract today; the surface is small on purpose.

per-user-cache-isolation

enforced webreact-nativeswiftandroid

Every identify(userId) switches the entitlement cache to a physically separate per-user storage slot — crossdeck:entitlements:<sha256(userId)> — and unconditionally wipes the in-memory snapshot. A user-switch on a shared device CANNOT cross-read a prior user's cached entitlements, even if the in-memory clear is somehow skipped, because the storage keys are physically separate. reset() wipes every per-user slot via the persisted index.

Code

  • sdks/web/src/entitlement-cache.ts
  • sdks/web/src/hash.ts
  • sdks/react-native/src/entitlement-cache.ts
  • sdks/swift/Sources/Crossdeck/EntitlementCache.swift
  • sdks/swift/Sources/Crossdeck/IdempotencyKey.swift
  • sdks/android/.../EntitlementCache.kt
  • sdks/android/.../IdempotencyKey.kt

Tests

  • web · entitlement-cache-isolation.test.ts
    identify(B) makes A's entitlements unreachable from in-memory
  • web · entitlement-cache-isolation.test.ts
    clearAll() removes every per-user storage key plus the index
  • web · entitlement-cache-isolation.test.ts
    a second cache instance reading A's storage suffix CANNOT see B's data
  • rn · entitlement-cache-isolation.test.ts
    identify(B) makes A's entitlements unreachable from in-memory
  • swift · EntitlementCacheIsolationTests.swift
    test_identifyB_makesAEntitlementsUnreachable
  • swift · EntitlementCacheIsolationTests.swift
    test_identifiedWritesLandUnderPerUserSha256Key
  • swift · EntitlementCacheIsolationTests.swift
    test_clearAll_removesEveryPerUserStorageKeyPlusIndex
  • android · EntitlementCacheIsolationTest.kt
    identify B makes A entitlements unreachable from in-memory
  • android · EntitlementCacheIsolationTest.kt
    clearAll removes every per-user storage key plus the index
  • android · EntitlementCacheIsolationTest.kt
    a fresh cache bound to A's key CANNOT read B's blob

Revenue   Revenue

The money paths. Cross-rail idempotency, StoreKit transaction discipline, App Store account-token shape, typed billing errors.

idempotency-key-deterministic

enforced webnodereact-nativeswiftandroidbackend

syncPurchases() on every SDK derives a deterministic Idempotency-Key from the request body (UUID-shaped SHA-256 of crossdeck:purchases/sync:<rail>:<jws|token>). Same input → same key. Backend short-circuits same-key-same-body retries by returning the cached response (status + body) with idempotent_replay: true flag in the body AND Idempotent-Replayed: true response header. Same-key-different-body returns 400 idempotency_key_in_use. 24-hour TTL matches Stripe. Cache only stores 2xx responses — 4xx/5xx pass through so callers can fix bugs and retry. Cross-SDK parity is CI-pinned: deriveForPurchase('apple', 'eyJ.jws.sig') MUST equal a66b1640-efaf-bb4d-1261-6650033bf111 on every SDK.

Code

  • sdks/web/src/idempotency-key.ts
  • sdks/react-native/src/idempotency-key.ts
  • sdks/node/src/idempotency-key.ts
  • sdks/swift/Sources/Crossdeck/IdempotencyKey.swift
  • sdks/android/.../IdempotencyKey.kt
  • backend/src/lib/idempotency-response-cache.ts
  • backend/src/api/v1-purchases.ts

Tests

  • web · idempotency-key.test.ts
    cross-SDK oracle — apple JWS pins canonical vector
  • web · idempotency-key.test.ts
    is deterministic: same body twice → identical key
  • web · idempotency-key.test.ts
    never silently falls back to a random key on missing identifier
  • node · idempotency-key.test.ts
    apple JWS produces the canonical pinned UUID across all 5 SDKs
  • swift · IdempotencyKeyTests.swift
    test_crossSdkOracle_appleJWS
  • swift · IdempotencyKeyTests.swift
    test_railNamespacing_preventsCrossRailCollisions
  • android · IdempotencyKeyTest.kt
    cross-SDK oracle for apple JWS
  • android · IdempotencyKeyTest.kt
    missing identifier returns null — never silent random fallback
  • backend · idempotency-response-cache.test.ts
    matches Stripe's 24-hour idempotency window
  • backend · idempotency-response-cache.test.ts
    injects idempotent_replay: true into a JSON object body

purchase-finish-iff-success

enforced swift

Swift PurchaseAutoTrack calls transaction.finish() STRICTLY inside the success branch of /purchases/sync. A 5xx response leaves the StoreKit transaction unfinished so Apple's re-delivery on the next session keeps the purchase alive — mid-process-death plus a transient backend outage CANNOT silently lose revenue. Failures are persisted to PendingPurchaseQueue for bounded in-session retry (max 5, exp backoff 30s / 1m / 5m / 30m / 2h).

Code

  • sdks/swift/.../PurchaseAutoTrack.swift
  • sdks/swift/.../PendingPurchaseQueue.swift

Tests

  • swift · PendingPurchaseQueueTests.swift
    test_shouldFinish_isTrueOnSuccess
  • swift · PendingPurchaseQueueTests.swift
    test_shouldFinish_isFalseOnAnyFailure
  • swift · PendingPurchaseQueueTests.swift
    test_recordFailure_persistsEntryWithBackoff
  • swift · PendingPurchaseQueueTests.swift
    test_recordFailure_dropsEntryAtCap

appaccounttoken-uuid-conformance

enforced swiftbackend

Swift SDK derives appAccountToken from developerUserId as a proper RFC 4122 UUID (passthrough if id is already a UUID; else UUID v5 from URL namespace + crossdeck:<id>; else omit). StoreKit's numeric originalTransactionId rides in its own dedicated wire field. Backend validator rejects non-UUID appAccountToken with 400.

Code

  • sdks/swift/.../AppAccountTokenDerivation.swift
  • sdks/swift/.../ServerEndpoints.swift
  • backend/src/api/v1-purchases-validation.ts

Tests

  • swift · AppAccountTokenDerivationTests.swift
    test_derive_returnsLowercaseUUIDDirectly_whenIdIsAlreadyUUID
  • swift · AppAccountTokenDerivationTests.swift
    test_derive_derivesUUIDv5_whenIdIsNotUUID
  • swift · AppAccountTokenDerivationTests.swift
    test_uuidV5_matchesRFCExample
  • backend · v1-purchases-validation.test.ts
    rejects malformed appAccountToken

billing-sync-typed-errors

enforced android

Android Google Play billing sync surfaces a typed CrossdeckError on every non-2xx /purchases/sync outcome. The pre-1.4.0 silent debug-log swallow is forbidden in money-path code by founder principle 3 (no silent failures).

Code

  • sdks/android/.../Crossdeck.kt
  • sdks/android/.../BillingAutoTrack.kt

Tests

  • android · BillingSyncOutcomeMapperTest.kt
    400 permanent outcome with backend envelope propagates typed CrossdeckError
  • android · BillingSyncOutcomeMapperTest.kt
    5xx retryable outcome with backend envelope propagates typed CrossdeckError

Errors   Errors

The wire vocabulary every SDK speaks when something goes wrong — same envelope, same code catalogue, same human-readable resolutions.

error-envelope-shape

enforced webnodereact-nativeswiftandroidbackend

Every v1 REST endpoint returns errors in a Stripe-shape envelope: { error: { type, code, message, request_id } } where type is one of authentication_error / permission_error / invalid_request_error / rate_limit_error / internal_error (the wire vocabulary in backend/src/api/v1-errors.ts ApiErrorType). HTTP status parity: invalid_request_error → 400, authentication_error → 401, permission_error → 403, rate_limit_error → 429, internal_error → 500. SDK-side clients parse via crossdeckErrorFromResponse (Web/Node/RN) / crossdeckErrorFrom(response:) (Swift) and surface the request_id verbatim so support traces are end-to-end joinable.

Code

  • backend/src/api/v1-errors.ts
  • sdks/web/src/errors.ts
  • sdks/node/src/errors.ts
  • sdks/react-native/src/errors.ts
  • sdks/swift/Sources/Crossdeck/Errors.swift
  • sdks/android/.../Errors.kt

Tests

  • swift · ErrorsTests.swift
    test_errorEnvelope_fallsBackOnGarbageBody
  • swift · ErrorsTests.swift
    test_errorEnvelope_reads_XRequestId_fallback
  • android · ErrorTypeWireVocabTest.kt
    backend 500 response parses to INTERNAL_ERROR

native-error-type-wire-vocab

enforced swiftandroid

Swift and Android SDK CrossdeckErrorType enums align with the backend's ApiErrorType wire vocabulary. New cases: internalError / INTERNAL_ERROR (matches backend's internal_error for 5xx) and configurationError / CONFIGURATION_ERROR (parity with TS SDKs). Deprecated cases apiError / API_ERROR and unknown / UNKNOWN are kept for source-compat but never emitted by new code paths — the backend NEVER sent those tokens.

Code

  • sdks/swift/Sources/Crossdeck/Errors.swift
  • sdks/android/.../Errors.kt

Tests

  • android · ErrorTypeWireVocabTest.kt
    5xx with no body falls back to INTERNAL_ERROR by status
  • android · ErrorTypeWireVocabTest.kt
    fromWire(internal_error) returns INTERNAL_ERROR
  • android · ErrorTypeWireVocabTest.kt
    fromWire(configuration_error) returns CONFIGURATION_ERROR
  • android · ErrorTypeWireVocabTest.kt
    deprecated API_ERROR + UNKNOWN still parse from wire for source-compat

sdk-error-codes-catalogue

enforced webnode

Web + Node SDK error-codes catalogues include EVERY backend-emitted ApiErrorCode with a description + resolution. Pre-v1.4.0 the catalogues documented codes the SDK threw ITSELF but ZERO backend codes — a developer hitting invalid_api_key / origin_not_allowed / bundle_id_not_allowed / env_mismatch / idempotency_key_in_use got undefined from getErrorCode(). v1.4.0 backfills the catalogue from backend/src/api/v1-errors.ts so every wire code has a canonical "what does this mean / what should I do" answer Stripe-style.

Code

  • sdks/web/src/error-codes.ts
  • sdks/node/src/error-codes.ts
  • backend/src/api/v1-errors.ts

Tests

  • web · error-codes-backfill.test.ts
    invalid_api_key resolution points at the dashboard
  • web · error-codes-backfill.test.ts
    idempotency_key_in_use resolution mentions Stripe-grade contract
  • web · error-codes-backfill.test.ts
    identity-lock codes carry permission_error type
  • web · error-codes-backfill.test.ts
    no entry has an empty description or resolution

Lifecycle   Lifecycle

Start, reset, stop, shutdown. Durable persistence across teardown, no observer leaks, no events lost.

init-reentry-drains-prior-queue

enforced webreact-native

Web + RN init() re-entry drains the prior EventQueue's pending setTimeout BEFORE replacing this.state. Pre-v1.4.0 the teardown handled autoTracker / webVitals / errors / unloadFlush but NOT events, so the prior queue's timer would fire AFTER the state swap — sending old-init events against new-init http + identity references (cross-identity leak during HMR / config swap / multi-tenant SDK shells). The teardown CANNOT call persistent.clear() — the durable queue belongs to the SDK lifetime, not the init() lifetime, and a survived crash mid-flush re-hydrates on the next init.

Code

  • sdks/web/src/crossdeck.ts
  • sdks/react-native/src/crossdeck.ts

Tests

  • web · init-reentry.test.ts
    re-init drains the prior queue's pending timer before swapping state
  • web · init-reentry.test.ts
    re-init does NOT wipe the durable event store

node-shutdown-awaits-flush

enforced node

Node SDK's async shutdown() awaits the internal flush() before tearing down the queue. A queue with pending events at sync-shutdown time (shutdownSync() or [Symbol.dispose]) logs a console.warn with the dropped-event count — silent loss is incompatible with the bank-grade contract. [Symbol.asyncDispose] is equivalent to await server.shutdown().

Code

  • sdks/node/src/crossdeck-server.ts

Tests

  • node · shutdown-flush.test.ts
    async shutdown() flushes queued events before clearing
  • node · shutdown-flush.test.ts
    async shutdown() proceeds with teardown even if flush fails
  • node · shutdown-flush.test.ts
    sync shutdownSync() warns when the buffer has events at teardown
  • node · shutdown-flush.test.ts
    [Symbol.asyncDispose] equals await server.shutdown()

swift-async-lifecycle

enforced swift

Swift Crossdeck.reset() and Crossdeck.stop() are async so the caller knows when teardown is durably complete. reset() awaits identity / entitlements / super-properties / breadcrumbs clear AND flips an isResetting tombstone synchronously at entry so isEntitled returns false IMMEDIATELY across the clear window. stop() awaits queue.persistAll() AND cancels stored boot + heartbeat Tasks. Sync escape hatches resetSync() + stopSync() exist for callers that cannot await.

Code

  • sdks/swift/Sources/Crossdeck/Crossdeck.swift

Tests

  • swift · AsyncLifecycleTests.swift
    test_reset_tombstone_flipsBeforeAsyncCompletion
  • swift · AsyncLifecycleTests.swift
    test_isEntitled_returnsFalseDuringResetWindow
  • swift · AsyncLifecycleTests.swift
    test_stop_isAsync_andAwaitsDurablePersist
  • swift · AsyncLifecycleTests.swift
    test_stopSync_runsTeardown_synchronously
  • swift · AsyncLifecycleTests.swift
    test_stop_cancelsStoredBackgroundTasks

swift-lifecycle-clean-teardown

enforced swift

Swift Crossdeck.stop() deregisters every NSNotificationCenter observer it installed via installLifecycleObservers (lifecycleObserverTokens drained with removeObserver) AND calls ErrorCapture.shared.uninstall() so the global exception handler releases its references to the stopped client. Apple platform caveat: NSSetUncaughtExceptionHandler has no removal API; uninstall() restores the chained prior handler so exceptions continue reaching it post-stop().

Code

  • sdks/swift/Sources/Crossdeck/Crossdeck.swift
  • sdks/swift/Sources/Crossdeck/ErrorCapture.swift

Tests

  • swift · LifecycleObserverCleanupTests.swift
    test_startedClient_capturesObserverTokens
  • swift · LifecycleObserverCleanupTests.swift
    test_stop_clearsAllObserverTokens
  • swift · LifecycleObserverCleanupTests.swift
    test_consecutiveLifecycle_doesNotAccumulateAcrossInstances

Analytics   Analytics

Cross-platform parity of the event pipeline. Same flush cadence, same property-merge precedence, same PII scrub posture, same funnel anchors.

flush-interval-parity

enforced webnodereact-nativeswiftandroid

Every Crossdeck SDK defaults its event-queue flush interval to 2000ms — the Stripe-adjacent industry norm. Pre-v1.4.0 the defaults disagreed (Web/Node 1500ms; RN/Swift/Android 5000ms), so cross-platform funnels saw events landing at different cadences. Per-instance override stays — call sites can still tune it freely.

Code

  • sdks/web/src/crossdeck.ts
  • sdks/node/src/crossdeck-server.ts
  • sdks/react-native/src/crossdeck.ts
  • sdks/swift/Sources/Crossdeck/EventQueue.swift
  • sdks/android/.../EventQueue.kt

Code-pinned defaults (no test names; the constant IS the contract)

  • swift · EventQueue.swift — flushIntervalMs: Int = 2_000
  • android · EventQueue.kt — flushIntervalMs: Long = 2_000L
  • web · crossdeck.ts — options.eventFlushIntervalMs ?? 2000
  • node · crossdeck-server.ts — options.eventFlushIntervalMs ?? 2000
  • rn · crossdeck.ts — options.eventFlushIntervalMs ?? 2000

super-property-merge-precedence

enforced swift

Every Crossdeck SDK merges event properties with the precedence device < super < caller (caller-supplied values win over registered super-properties, which win over auto-attached device info). Pre-v1.4.0 Swift had it INVERTED (super < device < caller — device clobbered super), so a register('plan', 'pro') super-property was silently overridden by auto-attached device fields whenever keys collided. Cross-SDK funnel queries on super-property keys returned different answers per platform.

Code

  • sdks/swift/.../EventPropertyMerge.swift
  • sdks/swift/.../Crossdeck.swift

Tests

  • swift · EventPropertyMergeTests.swift
    test_super_overrides_device
  • swift · EventPropertyMergeTests.swift
    test_caller_overrides_super
  • swift · EventPropertyMergeTests.swift
    test_full_precedence_chain
  • swift · EventPropertyMergeTests.swift
    test_matchesWebNodeRNPrecedence

node-pii-scrubber

enforced node

Node SDK's track() applies scrubPiiFromProperties on the enqueue path — parity with Web / RN / Swift. Pre-v1.4.0 the Node SDK was the ONLY one that skipped this, shipping every track() payload UNREDACTED despite the README promising parity. CrossdeckServerOptions.scrubPii defaults to true; explicit false opts out for regulator-required audit trails with a documented blast-radius warning.

Code

  • sdks/node/src/crossdeck-server.ts
  • sdks/node/src/types.ts
  • sdks/node/src/consent.ts

Tests

  • node · track-pii-scrub.test.ts
    by default redacts email-shaped values to <email>
  • node · track-pii-scrub.test.ts
    redacts card-number-shaped values to <card>
  • node · track-pii-scrub.test.ts
    walks nested maps + arrays
  • node · track-pii-scrub.test.ts
    scrubPii: false preserves the raw payload (opt-out)
  • node · track-pii-scrub.test.ts
    scrubPii: true is the default when option is omitted

rn-session-id-enrichment

enforced react-native

RN SDK's track() pipeline attaches a sessionId property to every event when the host has called setSessionId(...) — parity with the web SDK's session-anchored funnel queries. Pre-v1.4.0 the enrichment merged device + super + groups + caller but never carried sessionId, so cross-platform funnels on session anchors returned zero RN rows. The host owns session lifecycle (AppState + nav library); the SDK exposes setSessionId() / setSessionId(null) for the host to drive. Caller-supplied sessionId in properties still wins on conflict (matches the caller > super > device precedence chain).

Code

  • sdks/react-native/src/crossdeck.ts

Tests

  • rn · session-id-enrichment.test.ts
    track() events carry sessionId after setSessionId() is called
  • rn · session-id-enrichment.test.ts
    track() events do NOT carry sessionId before setSessionId() is called
  • rn · session-id-enrichment.test.ts
    setSessionId(null) clears the active session
  • rn · session-id-enrichment.test.ts
    caller-supplied sessionId property overrides setSessionId() value

sync-purchases-funnel-parity

enforced webnodereact-nativeswiftandroid

Manual syncPurchases() emits a purchase.completed analytics event on success across ALL SDKs. Pre-v1.4.0 only Swift / Android auto-track emitted it — Web / Node / RN manual calls + Swift / Android manual calls fired ZERO analytics. Schema mirrors the auto-track event name + rail / productId / subscriptionId so cross-platform funnels reconcile on every payment path. When the backend short-circuits via the v1.4.0 idempotency cache, the event also carries idempotent_replay: true.

Code

  • sdks/web/src/crossdeck.ts
  • sdks/node/src/crossdeck-server.ts
  • sdks/react-native/src/crossdeck.ts
  • sdks/swift/.../Crossdeck.swift
  • sdks/android/.../Crossdeck.kt

Tests

  • web · sync-purchases-funnel.test.ts
    emits purchase.completed after a successful sync
  • web · sync-purchases-funnel.test.ts
    carries idempotent_replay=true when backend replied from cache

Webhooks   Webhooks

HMAC verification, mandatory replay windows, distinguishable failure codes. One proposed entry that locks in outbound delivery's shape before the feature ships.

verifier-timestamp-mandatory

enforced node

Node verifyWebhookSignature() enforces a MANDATORY timestamp window. Pre-v1.4.0 the helper silently disabled replay protection on tolerance=0 and on Infinity / NaN / null. v1.4.0 rejects non-finite / negative / above-24h-cap tolerances at the boundary with typed webhook_invalid_tolerance and always runs the drift check. Verification failures are surfaced via distinguishable codes: webhook_signature_mismatch (wrong-secret signal), webhook_timestamp_outside_tolerance (replay-attack signal — alert separately), webhook_timestamp_missing, webhook_payload_not_json, webhook_missing_secret, webhook_invalid_tolerance.

Code

  • sdks/node/src/webhooks.ts
  • sdks/node/src/error-codes.ts

Tests

  • node · webhooks.test.ts
    tolerance of 0 still enforces the replay window (v1.4.0 — cannot disable)
  • node · webhooks.test.ts
    rejects Infinity tolerance (would silently disable replay protection)
  • node · webhooks.test.ts
    rejects NaN tolerance
  • node · webhooks.test.ts
    rejects negative tolerance
  • node · webhooks.test.ts
    rejects tolerance above the 24h cap
  • node · webhooks.test.ts
    rejects non-number tolerance (null / string)
  • node · webhooks.test.ts
    accepts tolerance exactly at the 24h cap
  • node · webhooks.test.ts
    malformed header (no t= or no v1=) throws webhook_timestamp_missing
  • node · webhooks.test.ts
    valid signature but non-JSON payload throws webhook_payload_not_json

documentation-honesty

enforced nodebackend

Customer-facing documentation honestly tags outbound webhook delivery as ROADMAP (no signer, no worker, no scheduler in backend/src yet). The Node verifier helper exists today for fixture authoring + locking the validation contract surface BEFORE delivery ships — its jsdoc carries an explicit [ROADMAP] disclaimer so a developer reading the source doesn't assume Crossdeck sends webhooks today. The rail-webhooks doc no longer claims state surfaces "through the dashboard, SDKs, and outbound webhooks" — outbound is gated to the explicit roadmap section.

Code

  • sdks/node/src/webhooks.ts
  • docs/rail-webhooks/index.html
  • docs/webhooks-receive/index.html

String matches

  • node · webhooks.ts — [ROADMAP — v1.4.0 honesty note]
  • docs · rail-webhooks/index.html — Outbound push-to-your-backend webhooks are roadmap
  • docs · webhooks-receive/index.html — This feature is on the roadmap

outbound-delivery-roadmap

proposed backend

Outbound webhook delivery contracts — locked in BEFORE delivery ships. When the signer + worker + scheduler + dead-letter dashboard surface land, this contract enforces: HMAC-SHA256 + timestamp signing (Stripe-compatible), exponential-backoff retry policy with 4xx hard-stop, 5-minute default replay window (configurable up to 24h via the same timestamp-validation helper), at-least-once delivery via persistent worker queue, dead-letter visibility in the dashboard for events that exhaust retries, and a stable event vocabulary (subscription.created / subscription.updated / subscription.canceled / customer.created / entitlement.granted / entitlement.revoked / etc.) documented in the dashboard schema before customers wire handlers.

Forward-looking code references

  • sdks/node/src/webhooks.ts (the verifier shape that the eventual sender will sign against)
  • docs/webhooks-receive/index.html

Forward-looking tests

  • node · webhooks.test.ts
    tolerance of 0 still enforces the replay window (v1.4.0 — cannot disable)
  • node · webhooks.test.ts
    rejects Infinity tolerance (would silently disable replay protection)

Diagnostics   Diagnostics

The shape of our own operational telemetry — the schema-lock that backs the independent-controller lawful basis.

contract-failed-payload-schema-lock

enforced webnodeswiftandroidreact-native

The crossdeck.contract_failed event payload contains ONLY the named diagnostic fields and never any end-user personal data. The wire shape is fixed — adding a new field requires (1) a pull request that updates this contract's allowedFields set, (2) a Privacy Policy §6 amendment, and (3) the Customer Disclosure Template / SDK Data Collection Reference §B updates. Per-SDK assertion tests enforce the field set on every release. This is the structural guarantee that backs the independent-controller lawful basis in the Privacy Policy: the payload remains diagnostic-only, not personal, so the legitimate-interest analysis stays valid as the SDK evolves.

Code

  • contracts/diagnostics/contract-failed-payload-schema-lock.json
  • backend/src/api/v1-sdk-diagnostic.ts
  • sdks/web/src/_diagnostic-telemetry.ts
  • sdks/node/src/_diagnostic-telemetry.ts
  • sdks/react-native/src/_diagnostic-telemetry.ts
  • sdks/swift/.../_DiagnosticTelemetry.swift
  • sdks/android/.../_DiagnosticTelemetry.kt

Tests

  • web · contract-failed-schema-lock.test.ts
    reportContractFailure payload conforms to schema-lock
  • node · contract-failed-schema-lock.test.ts
    reportContractFailure payload conforms to schema-lock
  • swift · ContractFailedSchemaLockTests.swift
    test_reportContractFailure_payloadFieldsAreInAllowList
  • swift · ContractFailedSchemaLockTests.swift
    test_reportContractFailure_doesNotEnterCustomerTrackPipeline
  • android · ContractFailedSchemaLockTest.kt
    reportContractFailure payload conforms to schema-lock
  • android · ContractFailedSchemaLockTest.kt
    reportContractFailure does not enter customer track pipeline
  • rn · contract-failed-schema-lock.test.ts
    reportContractFailure payload conforms to schema-lock
  • backend · v1-sdk-diagnostic.test.ts
    rejects payloads containing forbidden fields
  • backend · v1-sdk-diagnostic.test.ts
    rejects writes from non-reliability publishable keys
  • backend · v1-sdk-diagnostic.test.ts
    truncates source IP to network prefix before any logging

Verifying this independently

If you or your auditor want to verify the platform's contract posture without relying on this page, the primary evidence is in three places.

  1. The JSON. The canonical source of every contract is the set of JSON files under contracts/. The schema is fixed; every field on this page is either derived from one of these files or is meta-content. Diffing the directory between two SDK releases tells you exactly which structural guarantees changed.
  2. The CI runs. Every push to main runs three workflows: Test executes the per-SDK suites; Contract Audit matches every testRef name against the real test catalogue; Deploy Functions publishes the backend that serves the reliability endpoint. The logs are publicly readable per run.
  3. The SDK source. Every SDK is published to its public GitHub mirror (crossdeck-web, crossdeck-node, crossdeck-react-native, crossdeck-swift, crossdeck-android) and to the corresponding registry (npm, Swift Package Index, Maven Central). The runtime registry inside each artefact reads the same JSON that ships in the monorepo.

The SDK surface

Two API surfaces. CrossdeckContracts for reading the registry; reportContractFailure(...) for emitting the failure event.

Reading the registry

// Web / Node / React Native
import { CrossdeckContracts, type Contract } from "@cross-deck/web";

const all: readonly Contract[] = CrossdeckContracts.all();
const enforced: readonly Contract[] = all.filter((c) => c.status === "enforced");

const isolation: Contract | undefined =
  CrossdeckContracts.byId("per-user-cache-isolation");

const matchingTest: Contract | undefined =
  CrossdeckContracts.findByTestName("identify(B) makes A's entitlements unreachable from in-memory");

console.log("[crossdeck] bundled in", CrossdeckContracts.bundledIn);
console.log("[crossdeck] sdk version", CrossdeckContracts.sdkVersion);
// Swift
import Crossdeck

let all: [Contract] = CrossdeckContracts.allIncludingHistorical()
let enforced: [Contract] = CrossdeckContracts.all()

if let isolation = CrossdeckContracts.byId("per-user-cache-isolation") {
    assert(isolation.status == .enforced)
}

if let contract = CrossdeckContracts.findByTestName("test_identifyB_makesAEntitlementsUnreachable") {
    print(contract.id, contract.pillar)
}
// Android
import com.crossdeck.CrossdeckContracts
import com.crossdeck.ContractStatus

val enforced = CrossdeckContracts.all()
val isolation = CrossdeckContracts.byId("per-user-cache-isolation")
check(isolation?.status == ContractStatus.ENFORCED)

Reporting a failure

The helper is exposed on every SDK. The same shape, the same fields. The wire shape is schema-locked at contract-failed-payload-schema-lock.

// Vitest — typically in tests/_setup.ts
import { afterEach } from "vitest";
import { Crossdeck, CrossdeckContracts } from "@cross-deck/web";

afterEach((context) => {
  if (context.task.result?.state !== "fail") return;
  const contract = CrossdeckContracts.findByTestName(context.task.name);
  if (!contract) return;
  Crossdeck.reportContractFailure({
    contractId: contract.id,
    failureReason: context.task.result.errors?.[0]?.message ?? "unknown",
    runContext: process.env.CI ? "ci" : "dogfood",
    runId: process.env.GITHUB_RUN_ID ?? "local-" + Date.now(),
    testRef: { file: context.task.file?.filepath ?? "?", name: context.task.name },
  });
});
// XCTest — install an XCTestObservation in Tests/setUp
import XCTest

final class ContractFailureReporter: NSObject, XCTestObservation {
    func testCase(_ testCase: XCTestCase, didRecord issue: XCTIssue) {
        guard let contract = CrossdeckContracts.findByTestName(testCase.name) else { return }
        Crossdeck.shared.reportContractFailure(.init(
            contractId: contract.id,
            failureReason: issue.compactDescription,
            runContext: ProcessInfo.processInfo.environment["CI"] != nil ? .ci : .dogfood,
            runId: ProcessInfo.processInfo.environment["GITHUB_RUN_ID"] ?? UUID().uuidString,
            testRef: .init(file: issue.sourceCodeContext.location?.fileURL.lastPathComponent ?? "?",
                           name: testCase.name)
        ))
    }
}
// JUnit — apply as a @ClassRule on every contract-bearing test class
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import com.crossdeck.Crossdeck
import com.crossdeck.ContractFailureInput
import com.crossdeck.ContractFailureRunContext
import com.crossdeck.ContractTestRef
import com.crossdeck.CrossdeckContracts

class ContractFailureWatcher(private val cd: Crossdeck) : TestWatcher() {
    override fun failed(e: Throwable, description: Description) {
        val contract = CrossdeckContracts.findByTestName(description.methodName) ?: return
        cd.reportContractFailure(ContractFailureInput(
            contractId = contract.id,
            failureReason = e.message ?: e::class.qualifiedName ?: "unknown",
            runContext = if (System.getenv("CI") != null)
                ContractFailureRunContext.CI
            else
                ContractFailureRunContext.DOGFOOD,
            runId = System.getenv("GITHUB_RUN_ID") ?: java.util.UUID.randomUUID().toString(),
            testRef = ContractTestRef(file = description.className, name = description.methodName),
        ))
    }
}

Roadmap

One contract carries status: "proposed" at the time of writing. It's the forward-looking lock on outbound webhook delivery (outbound-delivery-roadmap) — the shape is fixed in the repository before the feature ships so that the signer, the worker, the scheduler, and the dashboard surface all land against the registered contract rather than drifting into existence.

New pillars and new contracts get added as the platform grows. The two that we expect to register in the next planning window:


Last updated May 27, 2026. Every contract on this page is enforced or proposed at HEAD of the monorepo's main branch. The corresponding JSON is the canonical source; this document is a rendering of it for human reading.