@cross-deck/node 1.9.1 → 1.10.0

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,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.10.0] — 2026-06-30
10
+
11
+ **Added — the blocking surface (Crossdeck Trust, v2 preview).** Blocking is
12
+ "entitlements, inverted": `getEntitlements()` answers *can they access Pro?*; the new
13
+ methods answer *should they be here at all?* — the same `/v1/resolve` plumbing, read for
14
+ `blocked`. Additive and non-breaking.
15
+
16
+ - **`resolve(input)`** → `ResolveResult` — the block verdict plus identity/entitlement
17
+ context in one call. **Fail-open by contract:** never throws for the verdict and never
18
+ returns `blocked: true` on uncertainty; any error → `{ blocked: false, degraded: true }`,
19
+ so a glitch can never lock out a real user (the mirror of entitlements, which fail
20
+ closed). Forward the end-user's `ip` (server-to-server callers otherwise expose only
21
+ their own server IP, so ip-rules can't match) and the verified identity
22
+ (`userId` + `idToken`) so domain/email rules can match.
23
+ - **`isBlocked(input)`** → `boolean` — convenience over `resolve()`; same fail-open.
24
+ - **`getOwnerStatus(input)`** → `BlockVerdict` — the public-page path: *"is the owner of
25
+ this page blocked?"*, no session token, for cache invalidation on link-in-bio / content
26
+ platforms (Pattern B).
27
+
28
+ New exported types: `ResolveInput`, `ResolveResult`, `BlockVerdict`, `OwnerStatusInput`.
29
+ All marked `@experimental` — Crossdeck Trust ships with Crossdeck v2; the shape is stable,
30
+ the surface is not yet GA.
31
+
9
32
  ## [1.9.1] — 2026-06-30
10
33
 
11
34
  **Docs — knowledge-backbone governance release.** No runtime API change. This
package/README.md CHANGED
@@ -614,8 +614,8 @@ CrossdeckContracts.byId("idempotency-key-deterministic");
614
614
  CrossdeckContracts.byPillar("revenue");
615
615
  CrossdeckContracts.withStatus("proposed");
616
616
  CrossdeckContracts.findByTestName("rail namespacing prevents cross-rail collisions");
617
- CrossdeckContracts.sdkVersion; // "1.9.0"
618
- CrossdeckContracts.bundledIn; // "@cross-deck/node@1.9.0"
617
+ CrossdeckContracts.sdkVersion; // "1.10.0"
618
+ CrossdeckContracts.bundledIn; // "@cross-deck/node@1.10.0"
619
619
  ```
620
620
 
621
621
  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-C4nWPQzH.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-C4nWPQzH.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.0";
187
+ readonly bundledIn: "@cross-deck/node@1.10.0";
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,82 @@ 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
+ }
441
517
  /**
442
518
  * Snapshot of one customer's last-known-good entitlements, as written
443
519
  * to / read from a durable `EntitlementStore`. Versioned for forward-
@@ -1456,6 +1532,40 @@ declare class CrossdeckServer extends EventEmitter {
1456
1532
  */
1457
1533
  getEntitlements(hints: IdentityHints, options?: RequestOptions): Promise<EntitlementsListResponse>;
1458
1534
  getCustomerEntitlements(customerId: string, options?: RequestOptions): Promise<EntitlementsListResponse>;
1535
+ /**
1536
+ * Resolve a user's BLOCK verdict (plus identity / entitlement context) — the same
1537
+ * `/v1/resolve` you'd call for entitlements, read for `blocked`. Wire it into signup
1538
+ * and every visit; on `blocked`, sign the user out (and, for public content, take
1539
+ * their pages offline — see the Blocking developer guide, Pattern B).
1540
+ *
1541
+ * **Fail-open is the contract.** This never throws for the block decision and never
1542
+ * returns `blocked: true` on uncertainty. Any error — Crossdeck unreachable, timeout,
1543
+ * unresolved identity — yields `{ blocked: false, degraded: true }`, so a glitch can
1544
+ * never lock out a real user.
1545
+ *
1546
+ * Forward the END USER's `ip` (you call this server-to-server, so otherwise Crossdeck
1547
+ * sees your server's IP and an ip-rule can't match the visitor), and the verified
1548
+ * identity (`userId` + `idToken`) so a `domain`/`email` rule can match the email.
1549
+ *
1550
+ * @experimental Preview — Crossdeck Trust ships with Crossdeck v2. Stable shape, not yet GA.
1551
+ */
1552
+ resolve(input: ResolveInput, options?: RequestOptions): Promise<ResolveResult>;
1553
+ /**
1554
+ * Convenience over {@link resolve}: just the boolean. Fail-open (false on any error).
1555
+ *
1556
+ * @experimental Preview — see {@link resolve}.
1557
+ */
1558
+ isBlocked(input: ResolveInput, options?: RequestOptions): Promise<boolean>;
1559
+ /**
1560
+ * The OWNER's block verdict for the public-page path (Pattern B / §5b of the Blocking
1561
+ * developer guide) — "is the owner of this page blocked?", no session token required.
1562
+ * Pass the owner's recorded identifiers (`userId`/`email`/`domain`/`ip`; the `ip` is
1563
+ * their CREATION ip, not a live visitor's). Poll on a short TTL to invalidate your
1564
+ * public-page cache. Fail-open — any error → `{ blocked: false, degraded: true }`.
1565
+ *
1566
+ * @experimental Preview — see {@link resolve}.
1567
+ */
1568
+ getOwnerStatus(input: OwnerStatusInput, options?: RequestOptions): Promise<BlockVerdict>;
1459
1569
  /**
1460
1570
  * Synchronous entitlement check. Returns `true` iff the customer
1461
1571
  * has the entitlement AND the cache entry is fresh (within
@@ -1935,4 +2045,4 @@ declare class CrossdeckServer extends EventEmitter {
1935
2045
  private normalizeIngestEvent;
1936
2046
  }
1937
2047
 
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 };
2048
+ export { type OwnerStatusInput 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 GrantDuration as Q, type GrantEntitlementInput as R, type GroupMembership as S, type HeartbeatResponse as T, type HttpRequestInfo as U, type HttpResponseInfo as V, type HttpRetriesConfig as W, type IdentifyOptions as X, type IdentityHints as Y, type IngestOptions as Z, type IngestResponse as _, type AliasResult as a, type PublicEntitlement as a0, type PurchaseResult as a1, type RequestOptions as a2, type ResolveInput as a3, type ResolveResult as a4, type RevokeEntitlementInput as a5, type RuntimeHost as a6, type RuntimeInfo as a7, type ServerEvent as a8, type StackFrame as a9, type StoredEntitlements as aa, type SyncPurchaseInput as ab, makeCrossdeckError as ac, 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.0";
187
+ readonly bundledIn: "@cross-deck/node@1.10.0";
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,82 @@ 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
+ }
441
517
  /**
442
518
  * Snapshot of one customer's last-known-good entitlements, as written
443
519
  * to / read from a durable `EntitlementStore`. Versioned for forward-
@@ -1456,6 +1532,40 @@ declare class CrossdeckServer extends EventEmitter {
1456
1532
  */
1457
1533
  getEntitlements(hints: IdentityHints, options?: RequestOptions): Promise<EntitlementsListResponse>;
1458
1534
  getCustomerEntitlements(customerId: string, options?: RequestOptions): Promise<EntitlementsListResponse>;
1535
+ /**
1536
+ * Resolve a user's BLOCK verdict (plus identity / entitlement context) — the same
1537
+ * `/v1/resolve` you'd call for entitlements, read for `blocked`. Wire it into signup
1538
+ * and every visit; on `blocked`, sign the user out (and, for public content, take
1539
+ * their pages offline — see the Blocking developer guide, Pattern B).
1540
+ *
1541
+ * **Fail-open is the contract.** This never throws for the block decision and never
1542
+ * returns `blocked: true` on uncertainty. Any error — Crossdeck unreachable, timeout,
1543
+ * unresolved identity — yields `{ blocked: false, degraded: true }`, so a glitch can
1544
+ * never lock out a real user.
1545
+ *
1546
+ * Forward the END USER's `ip` (you call this server-to-server, so otherwise Crossdeck
1547
+ * sees your server's IP and an ip-rule can't match the visitor), and the verified
1548
+ * identity (`userId` + `idToken`) so a `domain`/`email` rule can match the email.
1549
+ *
1550
+ * @experimental Preview — Crossdeck Trust ships with Crossdeck v2. Stable shape, not yet GA.
1551
+ */
1552
+ resolve(input: ResolveInput, options?: RequestOptions): Promise<ResolveResult>;
1553
+ /**
1554
+ * Convenience over {@link resolve}: just the boolean. Fail-open (false on any error).
1555
+ *
1556
+ * @experimental Preview — see {@link resolve}.
1557
+ */
1558
+ isBlocked(input: ResolveInput, options?: RequestOptions): Promise<boolean>;
1559
+ /**
1560
+ * The OWNER's block verdict for the public-page path (Pattern B / §5b of the Blocking
1561
+ * developer guide) — "is the owner of this page blocked?", no session token required.
1562
+ * Pass the owner's recorded identifiers (`userId`/`email`/`domain`/`ip`; the `ip` is
1563
+ * their CREATION ip, not a live visitor's). Poll on a short TTL to invalidate your
1564
+ * public-page cache. Fail-open — any error → `{ blocked: false, degraded: true }`.
1565
+ *
1566
+ * @experimental Preview — see {@link resolve}.
1567
+ */
1568
+ getOwnerStatus(input: OwnerStatusInput, options?: RequestOptions): Promise<BlockVerdict>;
1459
1569
  /**
1460
1570
  * Synchronous entitlement check. Returns `true` iff the customer
1461
1571
  * has the entitlement AND the cache entry is fresh (within
@@ -1935,4 +2045,4 @@ declare class CrossdeckServer extends EventEmitter {
1935
2045
  private normalizeIngestEvent;
1936
2046
  }
1937
2047
 
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 };
2048
+ export { type OwnerStatusInput 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 GrantDuration as Q, type GrantEntitlementInput as R, type GroupMembership as S, type HeartbeatResponse as T, type HttpRequestInfo as U, type HttpResponseInfo as V, type HttpRetriesConfig as W, type IdentifyOptions as X, type IdentityHints as Y, type IngestOptions as Z, type IngestResponse as _, type AliasResult as a, type PublicEntitlement as a0, type PurchaseResult as a1, type RequestOptions as a2, type ResolveInput as a3, type ResolveResult as a4, type RevokeEntitlementInput as a5, type RuntimeHost as a6, type RuntimeInfo as a7, type ServerEvent as a8, type StackFrame as a9, type StoredEntitlements as aa, type SyncPurchaseInput as ab, makeCrossdeckError as ac, 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 };
package/dist/index.cjs CHANGED
@@ -387,7 +387,7 @@ function byteLength(s) {
387
387
  var https = __toESM(require("https"));
388
388
 
389
389
  // src/_version.ts
390
- var SDK_VERSION = "1.9.1";
390
+ var SDK_VERSION = "1.10.0";
391
391
  var SDK_NAME = "@cross-deck/node";
392
392
 
393
393
  // src/_diagnostic-telemetry.ts
@@ -3149,6 +3149,130 @@ var CrossdeckServer = class extends import_node_events.EventEmitter {
3149
3149
  this.populateEntitlementCache({ customerId }, response);
3150
3150
  return response;
3151
3151
  }
3152
+ // ── Blocking — "entitlements, inverted" (Crossdeck Trust, v2 preview) ───────
3153
+ //
3154
+ // getEntitlements() answers "can they access Pro?"; resolve() answers "should
3155
+ // they be here at all?". Same call, opposite question. Both read Crossdeck live.
3156
+ // The safety inversion: entitlements fail CLOSED (protect revenue); blocking
3157
+ // fails OPEN (protect real users) — these methods NEVER throw for the verdict
3158
+ // and NEVER return blocked:true on uncertainty.
3159
+ /**
3160
+ * Resolve a user's BLOCK verdict (plus identity / entitlement context) — the same
3161
+ * `/v1/resolve` you'd call for entitlements, read for `blocked`. Wire it into signup
3162
+ * and every visit; on `blocked`, sign the user out (and, for public content, take
3163
+ * their pages offline — see the Blocking developer guide, Pattern B).
3164
+ *
3165
+ * **Fail-open is the contract.** This never throws for the block decision and never
3166
+ * returns `blocked: true` on uncertainty. Any error — Crossdeck unreachable, timeout,
3167
+ * unresolved identity — yields `{ blocked: false, degraded: true }`, so a glitch can
3168
+ * never lock out a real user.
3169
+ *
3170
+ * Forward the END USER's `ip` (you call this server-to-server, so otherwise Crossdeck
3171
+ * sees your server's IP and an ip-rule can't match the visitor), and the verified
3172
+ * identity (`userId` + `idToken`) so a `domain`/`email` rule can match the email.
3173
+ *
3174
+ * @experimental Preview — Crossdeck Trust ships with Crossdeck v2. Stable shape, not yet GA.
3175
+ */
3176
+ async resolve(input, options) {
3177
+ const failOpen = () => ({
3178
+ blocked: false,
3179
+ blockReason: null,
3180
+ blockedKey: null,
3181
+ degraded: true,
3182
+ status: "anonymous",
3183
+ entitlements: [],
3184
+ crossdeckCustomerId: null,
3185
+ anonymousId: input?.anonymousId ?? null,
3186
+ identityError: null
3187
+ });
3188
+ if (!input || !input.anonymousId && !(input.userId && input.idToken)) {
3189
+ this.debug.emit(
3190
+ "sdk.resolve_missing_identity",
3191
+ "resolve() needs anonymousId OR (userId + idToken); returning fail-open blocked:false."
3192
+ );
3193
+ return failOpen();
3194
+ }
3195
+ try {
3196
+ const raw = await this.http.request("POST", "/resolve", {
3197
+ body: {
3198
+ ...input.userId ? { userId: input.userId } : {},
3199
+ ...input.idToken ? { idToken: input.idToken } : {},
3200
+ ...input.anonymousId ? { anonymousId: input.anonymousId } : {},
3201
+ ...input.ip ? { ip: input.ip } : {}
3202
+ },
3203
+ signal: options?.signal,
3204
+ timeoutMs: options?.timeoutMs
3205
+ });
3206
+ return {
3207
+ blocked: raw.blocked === true,
3208
+ blockReason: raw.blockReason ?? null,
3209
+ blockedKey: raw.blockedKey ?? null,
3210
+ status: typeof raw.status === "string" ? raw.status : "active",
3211
+ entitlements: Array.isArray(raw.entitlements) ? raw.entitlements.filter((e) => typeof e === "string") : [],
3212
+ crossdeckCustomerId: raw.crossdeckCustomerId ?? raw.user?.id ?? null,
3213
+ anonymousId: raw.anonymousId ?? input.anonymousId ?? null,
3214
+ identityError: raw.identityError ?? null
3215
+ };
3216
+ } catch (err) {
3217
+ this.debug.emit(
3218
+ "sdk.resolve_failed",
3219
+ `resolve() failed \u2014 fail-open blocked:false. ${err instanceof Error ? err.message : String(err)}`,
3220
+ { error: err instanceof Error ? err.message : String(err) }
3221
+ );
3222
+ return failOpen();
3223
+ }
3224
+ }
3225
+ /**
3226
+ * Convenience over {@link resolve}: just the boolean. Fail-open (false on any error).
3227
+ *
3228
+ * @experimental Preview — see {@link resolve}.
3229
+ */
3230
+ async isBlocked(input, options) {
3231
+ return (await this.resolve(input, options)).blocked;
3232
+ }
3233
+ /**
3234
+ * The OWNER's block verdict for the public-page path (Pattern B / §5b of the Blocking
3235
+ * developer guide) — "is the owner of this page blocked?", no session token required.
3236
+ * Pass the owner's recorded identifiers (`userId`/`email`/`domain`/`ip`; the `ip` is
3237
+ * their CREATION ip, not a live visitor's). Poll on a short TTL to invalidate your
3238
+ * public-page cache. Fail-open — any error → `{ blocked: false, degraded: true }`.
3239
+ *
3240
+ * @experimental Preview — see {@link resolve}.
3241
+ */
3242
+ async getOwnerStatus(input, options) {
3243
+ const query = {
3244
+ userId: input?.userId || void 0,
3245
+ email: input?.email || void 0,
3246
+ domain: input?.domain || void 0,
3247
+ ip: input?.ip || void 0
3248
+ };
3249
+ if (!query.userId && !query.email && !query.domain && !query.ip) {
3250
+ this.debug.emit(
3251
+ "sdk.owner_status_missing_identity",
3252
+ "getOwnerStatus() needs at least one of userId/email/domain/ip; returning fail-open blocked:false."
3253
+ );
3254
+ return { blocked: false, blockReason: null, blockedKey: null, degraded: true };
3255
+ }
3256
+ try {
3257
+ const raw = await this.http.request("GET", "/trust/status", {
3258
+ query,
3259
+ signal: options?.signal,
3260
+ timeoutMs: options?.timeoutMs
3261
+ });
3262
+ return {
3263
+ blocked: raw.blocked === true,
3264
+ blockReason: raw.blockReason ?? null,
3265
+ blockedKey: raw.blockedKey ?? null
3266
+ };
3267
+ } catch (err) {
3268
+ this.debug.emit(
3269
+ "sdk.owner_status_failed",
3270
+ `getOwnerStatus() failed \u2014 fail-open blocked:false. ${err instanceof Error ? err.message : String(err)}`,
3271
+ { error: err instanceof Error ? err.message : String(err) }
3272
+ );
3273
+ return { blocked: false, blockReason: null, blockedKey: null, degraded: true };
3274
+ }
3275
+ }
3152
3276
  /**
3153
3277
  * Synchronous entitlement check. Returns `true` iff the customer
3154
3278
  * has the entitlement AND the cache entry is fresh (within
@@ -4596,8 +4720,8 @@ function normaliseSecrets(input) {
4596
4720
  }
4597
4721
 
4598
4722
  // src/_contracts-bundled.ts
4599
- var BUNDLED_IN = "@cross-deck/node@1.8.2";
4600
- var SDK_VERSION2 = "1.8.2";
4723
+ var BUNDLED_IN = "@cross-deck/node@1.10.0";
4724
+ var SDK_VERSION2 = "1.10.0";
4601
4725
  var BUNDLED_CONTRACTS = Object.freeze([
4602
4726
  {
4603
4727
  "id": "contract-failed-payload-schema-lock",
@@ -4719,7 +4843,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
4719
4843
  "legal/security/index.html#diagnostic",
4720
4844
  "legal/sdk-data/index.html#b-diagnostic"
4721
4845
  ],
4722
- "bundledIn": "@cross-deck/node@1.8.2"
4846
+ "bundledIn": "@cross-deck/node@1.10.0"
4723
4847
  },
4724
4848
  {
4725
4849
  "id": "documentation-honesty",
@@ -4751,7 +4875,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
4751
4875
  ],
4752
4876
  "registeredAt": "2026-05-26",
4753
4877
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 7.1",
4754
- "bundledIn": "@cross-deck/node@1.8.2"
4878
+ "bundledIn": "@cross-deck/node@1.10.0"
4755
4879
  },
4756
4880
  {
4757
4881
  "id": "error-envelope-shape",
@@ -4790,7 +4914,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
4790
4914
  ],
4791
4915
  "registeredAt": "2026-05-26",
4792
4916
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
4793
- "bundledIn": "@cross-deck/node@1.8.2"
4917
+ "bundledIn": "@cross-deck/node@1.10.0"
4794
4918
  },
4795
4919
  {
4796
4920
  "id": "flush-interval-parity",
@@ -4835,7 +4959,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
4835
4959
  ],
4836
4960
  "registeredAt": "2026-05-26",
4837
4961
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
4838
- "bundledIn": "@cross-deck/node@1.8.2"
4962
+ "bundledIn": "@cross-deck/node@1.10.0"
4839
4963
  },
4840
4964
  {
4841
4965
  "id": "idempotency-key-deterministic",
@@ -4940,7 +5064,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
4940
5064
  ],
4941
5065
  "registeredAt": "2026-05-26",
4942
5066
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
4943
- "bundledIn": "@cross-deck/node@1.8.2"
5067
+ "bundledIn": "@cross-deck/node@1.10.0"
4944
5068
  },
4945
5069
  {
4946
5070
  "id": "invalid-input-rejected-natively",
@@ -4996,7 +5120,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
4996
5120
  ],
4997
5121
  "registeredAt": "2026-06-11",
4998
5122
  "firstRegisteredIn": "swift trap-on-input class fix \u2014 first machine-tested Swift release",
4999
- "bundledIn": "@cross-deck/node@1.8.2"
5123
+ "bundledIn": "@cross-deck/node@1.10.0"
5000
5124
  },
5001
5125
  {
5002
5126
  "id": "node-pii-scrubber",
@@ -5035,7 +5159,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5035
5159
  ],
5036
5160
  "registeredAt": "2026-05-26",
5037
5161
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.1",
5038
- "bundledIn": "@cross-deck/node@1.8.2"
5162
+ "bundledIn": "@cross-deck/node@1.10.0"
5039
5163
  },
5040
5164
  {
5041
5165
  "id": "node-shutdown-awaits-flush",
@@ -5068,7 +5192,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5068
5192
  ],
5069
5193
  "registeredAt": "2026-05-26",
5070
5194
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.4",
5071
- "bundledIn": "@cross-deck/node@1.8.2"
5195
+ "bundledIn": "@cross-deck/node@1.10.0"
5072
5196
  },
5073
5197
  {
5074
5198
  "id": "sdk-error-codes-catalogue",
@@ -5113,7 +5237,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5113
5237
  ],
5114
5238
  "registeredAt": "2026-05-26",
5115
5239
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
5116
- "bundledIn": "@cross-deck/node@1.8.2"
5240
+ "bundledIn": "@cross-deck/node@1.10.0"
5117
5241
  },
5118
5242
  {
5119
5243
  "id": "sync-purchases-funnel-parity",
@@ -5146,7 +5270,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5146
5270
  ],
5147
5271
  "registeredAt": "2026-05-26",
5148
5272
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
5149
- "bundledIn": "@cross-deck/node@1.8.2"
5273
+ "bundledIn": "@cross-deck/node@1.10.0"
5150
5274
  },
5151
5275
  {
5152
5276
  "id": "verifier-timestamp-mandatory",
@@ -5200,7 +5324,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5200
5324
  ],
5201
5325
  "registeredAt": "2026-05-26",
5202
5326
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 7.2",
5203
- "bundledIn": "@cross-deck/node@1.8.2"
5327
+ "bundledIn": "@cross-deck/node@1.10.0"
5204
5328
  }
5205
5329
  ]);
5206
5330