@cross-deck/node 1.9.1 → 1.10.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
@@ -6,6 +6,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.10.1] — 2026-06-30
10
+
11
+ **Fixed — the blocking surface now works for the documented paths (still v2 preview).**
12
+
13
+ - `resolve()` / `isBlocked()` now accept a **bare `userId`** — the server-trusted
14
+ recognition path the backend honours (rides the identity backbone, matches on the email
15
+ Crossdeck already holds). 1.10.0's guard rejected it and fail-opened *without calling the
16
+ API*, so a known-user block silently never fired.
17
+ - **Added `gate({ email, ip })`** — the brand-new-signup door (`POST /v1/trust/gate`), the
18
+ only path that catches a farm Crossdeck has never seen. Fail-open by contract. New
19
+ `GateInput` / `GateVerdict` types.
20
+ - README now documents the blocking surface (the three doors + the verify-locally pattern).
21
+
22
+ Still `@experimental` — Crossdeck Trust ships with Crossdeck v2.
23
+
24
+
25
+ ## [1.10.0] — 2026-06-30
26
+
27
+ **Added — the blocking surface (Crossdeck Trust, v2 preview).** Blocking is
28
+ "entitlements, inverted": `getEntitlements()` answers *can they access Pro?*; the new
29
+ methods answer *should they be here at all?* — the same `/v1/resolve` plumbing, read for
30
+ `blocked`. Additive and non-breaking.
31
+
32
+ - **`resolve(input)`** → `ResolveResult` — the block verdict plus identity/entitlement
33
+ context in one call. **Fail-open by contract:** never throws for the verdict and never
34
+ returns `blocked: true` on uncertainty; any error → `{ blocked: false, degraded: true }`,
35
+ so a glitch can never lock out a real user (the mirror of entitlements, which fail
36
+ closed). Forward the end-user's `ip` (server-to-server callers otherwise expose only
37
+ their own server IP, so ip-rules can't match) and the verified identity
38
+ (`userId` + `idToken`) so domain/email rules can match.
39
+ - **`isBlocked(input)`** → `boolean` — convenience over `resolve()`; same fail-open.
40
+ - **`getOwnerStatus(input)`** → `BlockVerdict` — the public-page path: *"is the owner of
41
+ this page blocked?"*, no session token, for cache invalidation on link-in-bio / content
42
+ platforms (Pattern B).
43
+
44
+ New exported types: `ResolveInput`, `ResolveResult`, `BlockVerdict`, `OwnerStatusInput`.
45
+ All marked `@experimental` — Crossdeck Trust ships with Crossdeck v2; the shape is stable,
46
+ the surface is not yet GA.
47
+
9
48
  ## [1.9.1] — 2026-06-30
10
49
 
11
50
  **Docs — knowledge-backbone governance release.** No runtime API change. This
package/README.md CHANGED
@@ -211,6 +211,51 @@ app.post("/crossdeck-webhook", express.raw({ type: "application/json" }), (req,
211
211
 
212
212
  For test fixtures that need to mint signed webhooks against the same scheme, `signWebhookPayload(payload, secret, timestampSec)` is exported.
213
213
 
214
+ ### Blocking (Crossdeck Trust) — <sup>v2 preview</sup>
215
+
216
+ > **Preview — not GA.** The blocking surface launches **end-to-end with Crossdeck v2.**
217
+ > The shape is stable and all methods are `@experimental`, so it's documented and usable
218
+ > now (dogfooding / early access) — but it is **not** a launched feature yet, and this is
219
+ > not a fourth shipped USP. Treat it as preview until the v2 launch.
220
+
221
+ Blocking is **entitlements, inverted**: `getEntitlements()` asks *"can this user access Pro?"*; these ask *"should this user be here at all?"* — the **same identity backbone**, read for `blocked`. Every method is **fail-open by contract**: it never throws and never returns blocked on uncertainty, so a glitch can never lock out a real user.
222
+
223
+ There are three doors. Pick by who's at it:
224
+
225
+ ```ts
226
+ // 1. A user you ALREADY KNOW — a bare userId rides the backbone (no token, no setup).
227
+ // Crossdeck matches on the email it already holds. The common case.
228
+ const { blocked, blockReason } = await crossdeck.resolve({ userId, ip: req.ip });
229
+ if (blocked) { await signOut(); return showSuspended(blockReason); }
230
+ // or just the boolean: if (await crossdeck.isBlocked({ userId })) …
231
+
232
+ // 2. A BRAND-NEW signup Crossdeck has never seen — block by the two strings you have
233
+ // at signup, their email + ip. Call it BEFORE you create the account.
234
+ const gate = await crossdeck.gate({ email, ip: req.ip });
235
+ if (gate.action === "block") return rejectSignup(gate.blockReason);
236
+
237
+ // 3. A PUBLIC PAGE — "is the owner of this page blocked?" (no session token). Poll on a
238
+ // short TTL to invalidate your cache; the ip is the owner's CREATION ip.
239
+ const owner = await crossdeck.getOwnerStatus({ userId: ownerId });
240
+ if (owner.blocked) await takePageOffline(ownerId);
241
+ ```
242
+
243
+ **Recommended server pattern (no issuer registration):** if your backend runs Firebase
244
+ Admin, verify the ID token **locally** for the authoritative uid, then send that as a bare
245
+ `userId` — you keep the cryptographic proof on your side and Crossdeck needs zero setup:
246
+
247
+ ```ts
248
+ const { uid } = await getAuth().verifyIdToken(idToken); // your Firebase Admin
249
+ const { blocked } = await crossdeck.resolve({ userId: uid, ip: req.ip });
250
+ ```
251
+
252
+ Send `userId` + `idToken` to Crossdeck instead only if you *can't* verify locally — that's
253
+ the verified Tier-3 path, and the only one that needs a one-time auth-issuer registration.
254
+ Setting *who* is blocked is a rule in your Crossdeck admin. Full integration guide
255
+ (suspension pages, public-content takedown, the backfill sweep): `CROSSDECK_BLOCKING_DEVELOPER_GUIDE.md`.
256
+
257
+ Types: `ResolveInput` / `ResolveResult`, `GateInput` / `GateVerdict`, `OwnerStatusInput` / `BlockVerdict`.
258
+
214
259
  ## Cross-cutting
215
260
 
216
261
  ### Read-cost cross-match (Buckets)
@@ -614,8 +659,8 @@ CrossdeckContracts.byId("idempotency-key-deterministic");
614
659
  CrossdeckContracts.byPillar("revenue");
615
660
  CrossdeckContracts.withStatus("proposed");
616
661
  CrossdeckContracts.findByTestName("rail namespacing prevents cross-rail collisions");
617
- CrossdeckContracts.sdkVersion; // "1.9.0"
618
- CrossdeckContracts.bundledIn; // "@cross-deck/node@1.9.0"
662
+ CrossdeckContracts.sdkVersion; // "1.10.0"
663
+ CrossdeckContracts.bundledIn; // "@cross-deck/node@1.10.0"
619
664
  ```
620
665
 
621
666
  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).
@@ -1,4 +1,4 @@
1
- import { w as CrossdeckServer } from '../crossdeck-server-Q3CYJC4Z.mjs';
1
+ import { x as CrossdeckServer } from '../crossdeck-server-BpBVFC4O.mjs';
2
2
  import 'node:events';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { w as CrossdeckServer } from '../crossdeck-server-Q3CYJC4Z.js';
1
+ import { x as CrossdeckServer } from '../crossdeck-server-BpBVFC4O.js';
2
2
  import 'node:events';
3
3
 
4
4
  /**
@@ -183,8 +183,8 @@ declare const CrossdeckContracts: {
183
183
  readonly byId: (id: string) => Contract | undefined;
184
184
  readonly byPillar: (pillar: ContractPillar) => readonly Contract[];
185
185
  readonly withStatus: (status: ContractStatus) => readonly Contract[];
186
- readonly sdkVersion: "1.8.2";
187
- readonly bundledIn: "@cross-deck/node@1.8.2";
186
+ readonly sdkVersion: "1.10.1";
187
+ readonly bundledIn: "@cross-deck/node@1.10.1";
188
188
  /**
189
189
  * Resolve a failing test back to the contract it exercises.
190
190
  * Used by test-framework hooks to find the contract id of a
@@ -438,6 +438,117 @@ interface EntitlementsListResponse {
438
438
  crossdeckCustomerId: string;
439
439
  env: Environment;
440
440
  }
441
+ /**
442
+ * Identity for {@link CrossdeckServer.resolve} / {@link CrossdeckServer.isBlocked}.
443
+ * Send the END USER's identity, not your server's.
444
+ *
445
+ * @experimental Preview — Crossdeck Trust ships with Crossdeck v2. Shape is stable; the
446
+ * surface is not yet GA.
447
+ */
448
+ interface ResolveInput {
449
+ /** Your signed-in user id. */
450
+ userId?: string;
451
+ /** A SIGNED proof of that user (e.g. their Firebase ID token). Required alongside `userId` for the verified path. */
452
+ idToken?: string;
453
+ /** Anonymous device id (the Web SDK's `getAnonymousId()`). Folds a pre-login device. */
454
+ anonymousId?: string;
455
+ /**
456
+ * The END USER's IP. Forward it — you call resolve server-to-server, so without it
457
+ * Crossdeck sees YOUR server's IP and an ip-rule can't match the visitor.
458
+ */
459
+ ip?: string;
460
+ }
461
+ /**
462
+ * The block verdict. Fail-open: on any error these come back `{ blocked: false }`,
463
+ * never an exception — a glitch can never lock out a real user (the mirror of
464
+ * entitlements, which fail closed).
465
+ *
466
+ * @experimental Preview — see {@link ResolveInput}.
467
+ */
468
+ interface BlockVerdict {
469
+ /** True ONLY when Crossdeck holds an explicit block on this identity. */
470
+ blocked: boolean;
471
+ /** Why, when blocked: e.g. `"blocklist:domain"` | `"blocklist:ip"` | `"blocked"`. */
472
+ blockReason: string | null;
473
+ /** The exact rule that fired: `"domain:spartan.net"` | `"ip:1.2.3.4"` | `"manual"` | null. */
474
+ blockedKey: string | null;
475
+ /**
476
+ * True when this is the fail-open default (Crossdeck unreachable / identity unresolved).
477
+ * `blocked` is `false`; log it for observability, never treat it as a real "not blocked".
478
+ */
479
+ degraded?: boolean;
480
+ }
481
+ /**
482
+ * The full {@link CrossdeckServer.resolve} result — the block verdict plus the identity /
483
+ * entitlement context, so one call answers "should they be here?" and "what can they
484
+ * access?" together.
485
+ *
486
+ * @experimental Preview — see {@link ResolveInput}.
487
+ */
488
+ interface ResolveResult extends BlockVerdict {
489
+ /** `"active"` | `"anonymous"` | … the resolved identity status. */
490
+ status: string;
491
+ /** Active entitlement keys for this identity (e.g. `["pro"]`). */
492
+ entitlements: string[];
493
+ /** The canonical Crossdeck customer id, when resolved. */
494
+ crossdeckCustomerId: string | null;
495
+ /** The anonymous id Crossdeck resolved / assigned. */
496
+ anonymousId: string | null;
497
+ /**
498
+ * Set when a verified identity was sent but couldn't be checked (e.g. the auth issuer
499
+ * isn't registered). If you sent `userId`+`idToken` and `status` is `"anonymous"`, read this.
500
+ */
501
+ identityError: {
502
+ reason: string;
503
+ } | null;
504
+ }
505
+ /**
506
+ * Owner identifiers for {@link CrossdeckServer.getOwnerStatus} — the public-page path.
507
+ * Pass any subset; for a page-offline check the `ip` is the owner's CREATION ip.
508
+ *
509
+ * @experimental Preview — see {@link ResolveInput}.
510
+ */
511
+ interface OwnerStatusInput {
512
+ userId?: string;
513
+ email?: string;
514
+ domain?: string;
515
+ ip?: string;
516
+ }
517
+ /**
518
+ * Input for {@link CrossdeckServer.gate} — the brand-new-signup door. Pass the two strings
519
+ * you have at signup: `email` and `ip`. (Optional `domain`/`fingerprint` sharpen the score.)
520
+ *
521
+ * @experimental Preview — see {@link ResolveInput}.
522
+ */
523
+ interface GateInput {
524
+ /** The signing-up user's email. Matched against email + domain blocklist rules. */
525
+ email?: string;
526
+ /** The signing-up request's IP. Matched against ip rules + datacenter/velocity signals. */
527
+ ip?: string;
528
+ /** Optional explicit domain (defaults to the email's domain). */
529
+ domain?: string;
530
+ /** Optional device fingerprint — sharpens the composite signup score. */
531
+ fingerprint?: string;
532
+ }
533
+ /**
534
+ * The signup-gate verdict from {@link CrossdeckServer.gate}. Fail-open: on any error these
535
+ * come back `{ action: "allow", allow: true, degraded: true }` — a glitch never rejects a
536
+ * real signup.
537
+ *
538
+ * @experimental Preview — see {@link ResolveInput}.
539
+ */
540
+ interface GateVerdict {
541
+ /** `"allow"` | `"review"` | `"block"`. Reject the account only on `"block"`. */
542
+ action: "allow" | "review" | "block";
543
+ /** Convenience: `false` only when `action` is `"block"`. */
544
+ allow: boolean;
545
+ /** Why, when blocked: e.g. `"disposable_domain"` | `"blocklist:ip"` | `"blocklist:domain"`. */
546
+ blockReason: string | null;
547
+ /** The exact rule/key that fired, when blocked. */
548
+ blockedKey: string | null;
549
+ /** True when this is the fail-open default (Crossdeck unreachable). `allow` is `true`. */
550
+ degraded?: boolean;
551
+ }
441
552
  /**
442
553
  * Snapshot of one customer's last-known-good entitlements, as written
443
554
  * to / read from a durable `EntitlementStore`. Versioned for forward-
@@ -1456,6 +1567,53 @@ declare class CrossdeckServer extends EventEmitter {
1456
1567
  */
1457
1568
  getEntitlements(hints: IdentityHints, options?: RequestOptions): Promise<EntitlementsListResponse>;
1458
1569
  getCustomerEntitlements(customerId: string, options?: RequestOptions): Promise<EntitlementsListResponse>;
1570
+ /**
1571
+ * Resolve a user's BLOCK verdict (plus identity / entitlement context) — the same
1572
+ * `/v1/resolve` you'd call for entitlements, read for `blocked`. Wire it into signup
1573
+ * and every visit; on `blocked`, sign the user out (and, for public content, take
1574
+ * their pages offline — see the Blocking developer guide, Pattern B).
1575
+ *
1576
+ * **Fail-open is the contract.** This never throws for the block decision and never
1577
+ * returns `blocked: true` on uncertainty. Any error — Crossdeck unreachable, timeout,
1578
+ * unresolved identity — yields `{ blocked: false, degraded: true }`, so a glitch can
1579
+ * never lock out a real user.
1580
+ *
1581
+ * Forward the END USER's `ip` (you call this server-to-server, so otherwise Crossdeck
1582
+ * sees your server's IP and an ip-rule can't match the visitor), and the verified
1583
+ * identity (`userId` + `idToken`) so a `domain`/`email` rule can match the email.
1584
+ *
1585
+ * @experimental Preview — Crossdeck Trust ships with Crossdeck v2. Stable shape, not yet GA.
1586
+ */
1587
+ resolve(input: ResolveInput, options?: RequestOptions): Promise<ResolveResult>;
1588
+ /**
1589
+ * Convenience over {@link resolve}: just the boolean. Fail-open (false on any error).
1590
+ *
1591
+ * @experimental Preview — see {@link resolve}.
1592
+ */
1593
+ isBlocked(input: ResolveInput, options?: RequestOptions): Promise<boolean>;
1594
+ /**
1595
+ * The OWNER's block verdict for the public-page path (Pattern B / §5b of the Blocking
1596
+ * developer guide) — "is the owner of this page blocked?", no session token required.
1597
+ * Pass the owner's recorded identifiers (`userId`/`email`/`domain`/`ip`; the `ip` is
1598
+ * their CREATION ip, not a live visitor's). Poll on a short TTL to invalidate your
1599
+ * public-page cache. Fail-open — any error → `{ blocked: false, degraded: true }`.
1600
+ *
1601
+ * @experimental Preview — see {@link resolve}.
1602
+ */
1603
+ getOwnerStatus(input: OwnerStatusInput, options?: RequestOptions): Promise<BlockVerdict>;
1604
+ /**
1605
+ * The brand-new-signup door (§3 of the Blocking developer guide). Block a user Crossdeck
1606
+ * has **never seen** — at the instant they sign up — by the two strings you have then:
1607
+ * their `email` and `ip`. There's no identity to ride yet, so {@link resolve} can't catch
1608
+ * them; this matches `email` / domain / `ip` blocklist rules directly. Call it BEFORE you
1609
+ * create the account; on `action: "block"`, reject the signup.
1610
+ *
1611
+ * **Fail-open by contract** — never throws, never blocks on uncertainty: any error →
1612
+ * `{ action: "allow", allow: true, degraded: true }`, so a glitch can't reject a real signup.
1613
+ *
1614
+ * @experimental Preview — Crossdeck Trust ships with Crossdeck v2.
1615
+ */
1616
+ gate(input: GateInput, options?: RequestOptions): Promise<GateVerdict>;
1459
1617
  /**
1460
1618
  * Synchronous entitlement check. Returns `true` iff the customer
1461
1619
  * has the entitlement AND the cache entry is fresh (within
@@ -1935,4 +2093,4 @@ declare class CrossdeckServer extends EventEmitter {
1935
2093
  private normalizeIngestEvent;
1936
2094
  }
1937
2095
 
1938
- export { type PurchaseResult as $, type AliasIdentityInput as A, type Breadcrumb as B, CROSSDECK_API_VERSION as C, DEFAULT_BASE_URL as D, type Diagnostics as E, type EntitlementCacheOptions as F, type EntitlementMutationResult as G, type EntitlementStore as H, type EntitlementsListResponse as I, type EntitlementsListener as J, type Environment as K, type ErrorCaptureConfig as L, type ErrorLevel as M, type EventProperties as N, type ForgetResult as O, type GrantDuration as P, type GrantEntitlementInput as Q, type GroupMembership as R, type HeartbeatResponse as S, type HttpRequestInfo as T, type HttpResponseInfo as U, type HttpRetriesConfig as V, type IdentifyOptions as W, type IdentityHints as X, type IngestOptions as Y, type IngestResponse as Z, type PublicEntitlement as _, type AliasResult as a, type RequestOptions as a0, type RevokeEntitlementInput as a1, type RuntimeHost as a2, type RuntimeInfo as a3, type ServerEvent as a4, type StackFrame as a5, type StoredEntitlements as a6, type SyncPurchaseInput as a7, makeCrossdeckError as a8, type AuditDecision as b, type AuditEntry as c, type BreadcrumbCategory as d, type BreadcrumbLevel as e, type CapturedError as f, type Contract as g, type ContractAppliesTo as h, type ContractFailureInput as i, type ContractPillar as j, type ContractStatus as k, type ContractTestRef as l, CrossdeckAuthenticationError as m, CrossdeckConfigurationError as n, CrossdeckContracts as o, CrossdeckError as p, type CrossdeckErrorPayload as q, type CrossdeckErrorType as r, CrossdeckInternalError as s, CrossdeckNetworkError as t, CrossdeckPermissionError as u, CrossdeckRateLimitError as v, CrossdeckServer as w, type CrossdeckServerOptions as x, CrossdeckValidationError as y, DEFAULT_TIMEOUT_MS as z };
2096
+ export { type IngestOptions as $, type AliasIdentityInput as A, type BlockVerdict as B, CROSSDECK_API_VERSION as C, DEFAULT_BASE_URL as D, DEFAULT_TIMEOUT_MS as E, type Diagnostics as F, type EntitlementCacheOptions as G, type EntitlementMutationResult as H, type EntitlementStore as I, type EntitlementsListResponse as J, type EntitlementsListener as K, type Environment as L, type ErrorCaptureConfig as M, type ErrorLevel as N, type EventProperties as O, type ForgetResult as P, type GateInput as Q, type GateVerdict as R, type GrantDuration as S, type GrantEntitlementInput as T, type GroupMembership as U, type HeartbeatResponse as V, type HttpRequestInfo as W, type HttpResponseInfo as X, type HttpRetriesConfig as Y, type IdentifyOptions as Z, type IdentityHints as _, type AliasResult as a, type IngestResponse as a0, type OwnerStatusInput as a1, type PublicEntitlement as a2, type PurchaseResult as a3, type RequestOptions as a4, type ResolveInput as a5, type ResolveResult as a6, type RevokeEntitlementInput as a7, type RuntimeHost as a8, type RuntimeInfo as a9, type ServerEvent as aa, type StackFrame as ab, type StoredEntitlements as ac, type SyncPurchaseInput as ad, makeCrossdeckError as ae, type AuditDecision as b, type AuditEntry as c, type Breadcrumb as d, type BreadcrumbCategory as e, type BreadcrumbLevel as f, type CapturedError as g, type Contract as h, type ContractAppliesTo as i, type ContractFailureInput as j, type ContractPillar as k, type ContractStatus as l, type ContractTestRef as m, CrossdeckAuthenticationError as n, CrossdeckConfigurationError as o, CrossdeckContracts as p, CrossdeckError as q, type CrossdeckErrorPayload as r, type CrossdeckErrorType as s, CrossdeckInternalError as t, CrossdeckNetworkError as u, CrossdeckPermissionError as v, CrossdeckRateLimitError as w, CrossdeckServer as x, type CrossdeckServerOptions as y, CrossdeckValidationError as z };
@@ -183,8 +183,8 @@ declare const CrossdeckContracts: {
183
183
  readonly byId: (id: string) => Contract | undefined;
184
184
  readonly byPillar: (pillar: ContractPillar) => readonly Contract[];
185
185
  readonly withStatus: (status: ContractStatus) => readonly Contract[];
186
- readonly sdkVersion: "1.8.2";
187
- readonly bundledIn: "@cross-deck/node@1.8.2";
186
+ readonly sdkVersion: "1.10.1";
187
+ readonly bundledIn: "@cross-deck/node@1.10.1";
188
188
  /**
189
189
  * Resolve a failing test back to the contract it exercises.
190
190
  * Used by test-framework hooks to find the contract id of a
@@ -438,6 +438,117 @@ interface EntitlementsListResponse {
438
438
  crossdeckCustomerId: string;
439
439
  env: Environment;
440
440
  }
441
+ /**
442
+ * Identity for {@link CrossdeckServer.resolve} / {@link CrossdeckServer.isBlocked}.
443
+ * Send the END USER's identity, not your server's.
444
+ *
445
+ * @experimental Preview — Crossdeck Trust ships with Crossdeck v2. Shape is stable; the
446
+ * surface is not yet GA.
447
+ */
448
+ interface ResolveInput {
449
+ /** Your signed-in user id. */
450
+ userId?: string;
451
+ /** A SIGNED proof of that user (e.g. their Firebase ID token). Required alongside `userId` for the verified path. */
452
+ idToken?: string;
453
+ /** Anonymous device id (the Web SDK's `getAnonymousId()`). Folds a pre-login device. */
454
+ anonymousId?: string;
455
+ /**
456
+ * The END USER's IP. Forward it — you call resolve server-to-server, so without it
457
+ * Crossdeck sees YOUR server's IP and an ip-rule can't match the visitor.
458
+ */
459
+ ip?: string;
460
+ }
461
+ /**
462
+ * The block verdict. Fail-open: on any error these come back `{ blocked: false }`,
463
+ * never an exception — a glitch can never lock out a real user (the mirror of
464
+ * entitlements, which fail closed).
465
+ *
466
+ * @experimental Preview — see {@link ResolveInput}.
467
+ */
468
+ interface BlockVerdict {
469
+ /** True ONLY when Crossdeck holds an explicit block on this identity. */
470
+ blocked: boolean;
471
+ /** Why, when blocked: e.g. `"blocklist:domain"` | `"blocklist:ip"` | `"blocked"`. */
472
+ blockReason: string | null;
473
+ /** The exact rule that fired: `"domain:spartan.net"` | `"ip:1.2.3.4"` | `"manual"` | null. */
474
+ blockedKey: string | null;
475
+ /**
476
+ * True when this is the fail-open default (Crossdeck unreachable / identity unresolved).
477
+ * `blocked` is `false`; log it for observability, never treat it as a real "not blocked".
478
+ */
479
+ degraded?: boolean;
480
+ }
481
+ /**
482
+ * The full {@link CrossdeckServer.resolve} result — the block verdict plus the identity /
483
+ * entitlement context, so one call answers "should they be here?" and "what can they
484
+ * access?" together.
485
+ *
486
+ * @experimental Preview — see {@link ResolveInput}.
487
+ */
488
+ interface ResolveResult extends BlockVerdict {
489
+ /** `"active"` | `"anonymous"` | … the resolved identity status. */
490
+ status: string;
491
+ /** Active entitlement keys for this identity (e.g. `["pro"]`). */
492
+ entitlements: string[];
493
+ /** The canonical Crossdeck customer id, when resolved. */
494
+ crossdeckCustomerId: string | null;
495
+ /** The anonymous id Crossdeck resolved / assigned. */
496
+ anonymousId: string | null;
497
+ /**
498
+ * Set when a verified identity was sent but couldn't be checked (e.g. the auth issuer
499
+ * isn't registered). If you sent `userId`+`idToken` and `status` is `"anonymous"`, read this.
500
+ */
501
+ identityError: {
502
+ reason: string;
503
+ } | null;
504
+ }
505
+ /**
506
+ * Owner identifiers for {@link CrossdeckServer.getOwnerStatus} — the public-page path.
507
+ * Pass any subset; for a page-offline check the `ip` is the owner's CREATION ip.
508
+ *
509
+ * @experimental Preview — see {@link ResolveInput}.
510
+ */
511
+ interface OwnerStatusInput {
512
+ userId?: string;
513
+ email?: string;
514
+ domain?: string;
515
+ ip?: string;
516
+ }
517
+ /**
518
+ * Input for {@link CrossdeckServer.gate} — the brand-new-signup door. Pass the two strings
519
+ * you have at signup: `email` and `ip`. (Optional `domain`/`fingerprint` sharpen the score.)
520
+ *
521
+ * @experimental Preview — see {@link ResolveInput}.
522
+ */
523
+ interface GateInput {
524
+ /** The signing-up user's email. Matched against email + domain blocklist rules. */
525
+ email?: string;
526
+ /** The signing-up request's IP. Matched against ip rules + datacenter/velocity signals. */
527
+ ip?: string;
528
+ /** Optional explicit domain (defaults to the email's domain). */
529
+ domain?: string;
530
+ /** Optional device fingerprint — sharpens the composite signup score. */
531
+ fingerprint?: string;
532
+ }
533
+ /**
534
+ * The signup-gate verdict from {@link CrossdeckServer.gate}. Fail-open: on any error these
535
+ * come back `{ action: "allow", allow: true, degraded: true }` — a glitch never rejects a
536
+ * real signup.
537
+ *
538
+ * @experimental Preview — see {@link ResolveInput}.
539
+ */
540
+ interface GateVerdict {
541
+ /** `"allow"` | `"review"` | `"block"`. Reject the account only on `"block"`. */
542
+ action: "allow" | "review" | "block";
543
+ /** Convenience: `false` only when `action` is `"block"`. */
544
+ allow: boolean;
545
+ /** Why, when blocked: e.g. `"disposable_domain"` | `"blocklist:ip"` | `"blocklist:domain"`. */
546
+ blockReason: string | null;
547
+ /** The exact rule/key that fired, when blocked. */
548
+ blockedKey: string | null;
549
+ /** True when this is the fail-open default (Crossdeck unreachable). `allow` is `true`. */
550
+ degraded?: boolean;
551
+ }
441
552
  /**
442
553
  * Snapshot of one customer's last-known-good entitlements, as written
443
554
  * to / read from a durable `EntitlementStore`. Versioned for forward-
@@ -1456,6 +1567,53 @@ declare class CrossdeckServer extends EventEmitter {
1456
1567
  */
1457
1568
  getEntitlements(hints: IdentityHints, options?: RequestOptions): Promise<EntitlementsListResponse>;
1458
1569
  getCustomerEntitlements(customerId: string, options?: RequestOptions): Promise<EntitlementsListResponse>;
1570
+ /**
1571
+ * Resolve a user's BLOCK verdict (plus identity / entitlement context) — the same
1572
+ * `/v1/resolve` you'd call for entitlements, read for `blocked`. Wire it into signup
1573
+ * and every visit; on `blocked`, sign the user out (and, for public content, take
1574
+ * their pages offline — see the Blocking developer guide, Pattern B).
1575
+ *
1576
+ * **Fail-open is the contract.** This never throws for the block decision and never
1577
+ * returns `blocked: true` on uncertainty. Any error — Crossdeck unreachable, timeout,
1578
+ * unresolved identity — yields `{ blocked: false, degraded: true }`, so a glitch can
1579
+ * never lock out a real user.
1580
+ *
1581
+ * Forward the END USER's `ip` (you call this server-to-server, so otherwise Crossdeck
1582
+ * sees your server's IP and an ip-rule can't match the visitor), and the verified
1583
+ * identity (`userId` + `idToken`) so a `domain`/`email` rule can match the email.
1584
+ *
1585
+ * @experimental Preview — Crossdeck Trust ships with Crossdeck v2. Stable shape, not yet GA.
1586
+ */
1587
+ resolve(input: ResolveInput, options?: RequestOptions): Promise<ResolveResult>;
1588
+ /**
1589
+ * Convenience over {@link resolve}: just the boolean. Fail-open (false on any error).
1590
+ *
1591
+ * @experimental Preview — see {@link resolve}.
1592
+ */
1593
+ isBlocked(input: ResolveInput, options?: RequestOptions): Promise<boolean>;
1594
+ /**
1595
+ * The OWNER's block verdict for the public-page path (Pattern B / §5b of the Blocking
1596
+ * developer guide) — "is the owner of this page blocked?", no session token required.
1597
+ * Pass the owner's recorded identifiers (`userId`/`email`/`domain`/`ip`; the `ip` is
1598
+ * their CREATION ip, not a live visitor's). Poll on a short TTL to invalidate your
1599
+ * public-page cache. Fail-open — any error → `{ blocked: false, degraded: true }`.
1600
+ *
1601
+ * @experimental Preview — see {@link resolve}.
1602
+ */
1603
+ getOwnerStatus(input: OwnerStatusInput, options?: RequestOptions): Promise<BlockVerdict>;
1604
+ /**
1605
+ * The brand-new-signup door (§3 of the Blocking developer guide). Block a user Crossdeck
1606
+ * has **never seen** — at the instant they sign up — by the two strings you have then:
1607
+ * their `email` and `ip`. There's no identity to ride yet, so {@link resolve} can't catch
1608
+ * them; this matches `email` / domain / `ip` blocklist rules directly. Call it BEFORE you
1609
+ * create the account; on `action: "block"`, reject the signup.
1610
+ *
1611
+ * **Fail-open by contract** — never throws, never blocks on uncertainty: any error →
1612
+ * `{ action: "allow", allow: true, degraded: true }`, so a glitch can't reject a real signup.
1613
+ *
1614
+ * @experimental Preview — Crossdeck Trust ships with Crossdeck v2.
1615
+ */
1616
+ gate(input: GateInput, options?: RequestOptions): Promise<GateVerdict>;
1459
1617
  /**
1460
1618
  * Synchronous entitlement check. Returns `true` iff the customer
1461
1619
  * has the entitlement AND the cache entry is fresh (within
@@ -1935,4 +2093,4 @@ declare class CrossdeckServer extends EventEmitter {
1935
2093
  private normalizeIngestEvent;
1936
2094
  }
1937
2095
 
1938
- export { type PurchaseResult as $, type AliasIdentityInput as A, type Breadcrumb as B, CROSSDECK_API_VERSION as C, DEFAULT_BASE_URL as D, type Diagnostics as E, type EntitlementCacheOptions as F, type EntitlementMutationResult as G, type EntitlementStore as H, type EntitlementsListResponse as I, type EntitlementsListener as J, type Environment as K, type ErrorCaptureConfig as L, type ErrorLevel as M, type EventProperties as N, type ForgetResult as O, type GrantDuration as P, type GrantEntitlementInput as Q, type GroupMembership as R, type HeartbeatResponse as S, type HttpRequestInfo as T, type HttpResponseInfo as U, type HttpRetriesConfig as V, type IdentifyOptions as W, type IdentityHints as X, type IngestOptions as Y, type IngestResponse as Z, type PublicEntitlement as _, type AliasResult as a, type RequestOptions as a0, type RevokeEntitlementInput as a1, type RuntimeHost as a2, type RuntimeInfo as a3, type ServerEvent as a4, type StackFrame as a5, type StoredEntitlements as a6, type SyncPurchaseInput as a7, makeCrossdeckError as a8, type AuditDecision as b, type AuditEntry as c, type BreadcrumbCategory as d, type BreadcrumbLevel as e, type CapturedError as f, type Contract as g, type ContractAppliesTo as h, type ContractFailureInput as i, type ContractPillar as j, type ContractStatus as k, type ContractTestRef as l, CrossdeckAuthenticationError as m, CrossdeckConfigurationError as n, CrossdeckContracts as o, CrossdeckError as p, type CrossdeckErrorPayload as q, type CrossdeckErrorType as r, CrossdeckInternalError as s, CrossdeckNetworkError as t, CrossdeckPermissionError as u, CrossdeckRateLimitError as v, CrossdeckServer as w, type CrossdeckServerOptions as x, CrossdeckValidationError as y, DEFAULT_TIMEOUT_MS as z };
2096
+ export { type IngestOptions as $, type AliasIdentityInput as A, type BlockVerdict as B, CROSSDECK_API_VERSION as C, DEFAULT_BASE_URL as D, DEFAULT_TIMEOUT_MS as E, type Diagnostics as F, type EntitlementCacheOptions as G, type EntitlementMutationResult as H, type EntitlementStore as I, type EntitlementsListResponse as J, type EntitlementsListener as K, type Environment as L, type ErrorCaptureConfig as M, type ErrorLevel as N, type EventProperties as O, type ForgetResult as P, type GateInput as Q, type GateVerdict as R, type GrantDuration as S, type GrantEntitlementInput as T, type GroupMembership as U, type HeartbeatResponse as V, type HttpRequestInfo as W, type HttpResponseInfo as X, type HttpRetriesConfig as Y, type IdentifyOptions as Z, type IdentityHints as _, type AliasResult as a, type IngestResponse as a0, type OwnerStatusInput as a1, type PublicEntitlement as a2, type PurchaseResult as a3, type RequestOptions as a4, type ResolveInput as a5, type ResolveResult as a6, type RevokeEntitlementInput as a7, type RuntimeHost as a8, type RuntimeInfo as a9, type ServerEvent as aa, type StackFrame as ab, type StoredEntitlements as ac, type SyncPurchaseInput as ad, makeCrossdeckError as ae, type AuditDecision as b, type AuditEntry as c, type Breadcrumb as d, type BreadcrumbCategory as e, type BreadcrumbLevel as f, type CapturedError as g, type Contract as h, type ContractAppliesTo as i, type ContractFailureInput as j, type ContractPillar as k, type ContractStatus as l, type ContractTestRef as m, CrossdeckAuthenticationError as n, CrossdeckConfigurationError as o, CrossdeckContracts as p, CrossdeckError as q, type CrossdeckErrorPayload as r, type CrossdeckErrorType as s, CrossdeckInternalError as t, CrossdeckNetworkError as u, CrossdeckPermissionError as v, CrossdeckRateLimitError as w, CrossdeckServer as x, type CrossdeckServerOptions as y, CrossdeckValidationError as z };