@cross-deck/web 1.6.3 → 1.6.4

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/dist/index.d.mts CHANGED
@@ -247,9 +247,9 @@ declare const CrossdeckContracts: {
247
247
  /** Filter by lifecycle status. */
248
248
  readonly withStatus: (status: ContractStatus) => readonly Contract[];
249
249
  /** Semver of the SDK release these contracts were bundled with. */
250
- readonly sdkVersion: "1.6.3";
250
+ readonly sdkVersion: "1.6.4";
251
251
  /** Fully-qualified bundle identifier — e.g. `@cross-deck/web@1.4.2`. */
252
- readonly bundledIn: "@cross-deck/web@1.6.3";
252
+ readonly bundledIn: "@cross-deck/web@1.6.4";
253
253
  /**
254
254
  * Resolve a failing test back to the contract it exercises.
255
255
  * Used by test-framework hooks (Vitest `afterEach`, XCTest
@@ -462,6 +462,7 @@ declare class CrossdeckClient {
462
462
  private verifiers;
463
463
  private verifierReporter;
464
464
  private verifierCtx;
465
+ private flushIntervalMs;
465
466
  /**
466
467
  * Boot the SDK. Idempotent — calling init twice with the same options
467
468
  * is a no-op; calling with different options replaces the previous
@@ -936,7 +937,7 @@ declare class MemoryStorage implements KeyValueStorage {
936
937
  *
937
938
  * Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.
938
939
  */
939
- declare const SDK_VERSION = "1.6.3";
940
+ declare const SDK_VERSION = "1.6.4";
940
941
  declare const SDK_NAME = "@cross-deck/web";
941
942
 
942
943
  /**
package/dist/index.d.ts CHANGED
@@ -247,9 +247,9 @@ declare const CrossdeckContracts: {
247
247
  /** Filter by lifecycle status. */
248
248
  readonly withStatus: (status: ContractStatus) => readonly Contract[];
249
249
  /** Semver of the SDK release these contracts were bundled with. */
250
- readonly sdkVersion: "1.6.3";
250
+ readonly sdkVersion: "1.6.4";
251
251
  /** Fully-qualified bundle identifier — e.g. `@cross-deck/web@1.4.2`. */
252
- readonly bundledIn: "@cross-deck/web@1.6.3";
252
+ readonly bundledIn: "@cross-deck/web@1.6.4";
253
253
  /**
254
254
  * Resolve a failing test back to the contract it exercises.
255
255
  * Used by test-framework hooks (Vitest `afterEach`, XCTest
@@ -462,6 +462,7 @@ declare class CrossdeckClient {
462
462
  private verifiers;
463
463
  private verifierReporter;
464
464
  private verifierCtx;
465
+ private flushIntervalMs;
465
466
  /**
466
467
  * Boot the SDK. Idempotent — calling init twice with the same options
467
468
  * is a no-op; calling with different options replaces the previous
@@ -936,7 +937,7 @@ declare class MemoryStorage implements KeyValueStorage {
936
937
  *
937
938
  * Do NOT edit by hand — `node scripts/sync-sdk-versions.mjs`.
938
939
  */
939
- declare const SDK_VERSION = "1.6.3";
940
+ declare const SDK_VERSION = "1.6.4";
940
941
  declare const SDK_NAME = "@cross-deck/web";
941
942
 
942
943
  /**
package/dist/index.mjs CHANGED
@@ -64,7 +64,7 @@ function typeMapForStatus(status) {
64
64
  }
65
65
 
66
66
  // src/_version.ts
67
- var SDK_VERSION = "1.6.3";
67
+ var SDK_VERSION = "1.6.4";
68
68
  var SDK_NAME = "@cross-deck/web";
69
69
 
70
70
  // src/http.ts
@@ -2830,6 +2830,15 @@ var VerifierReporter = class {
2830
2830
  constructor(ctx) {
2831
2831
  this.ctx = ctx;
2832
2832
  this.reentrancyDepth = 0;
2833
+ /** The live verifier set — attached by the SDK right after init so the
2834
+ * emit path can schema-lock its OWN outgoing telemetry against the real
2835
+ * payload. Null until attached (boot-time reports still work without it). */
2836
+ this.verifiers = null;
2837
+ }
2838
+ /** Hand the reporter the live verifier set so `reportFail` can run the
2839
+ * `onReportContractFailure` hook on the exact payload it's about to emit. */
2840
+ attachVerifiers(verifiers) {
2841
+ this.verifiers = verifiers;
2833
2842
  }
2834
2843
  /**
2835
2844
  * Report a verifier result. Pass `operation` for hot-path results
@@ -2858,7 +2867,7 @@ var VerifierReporter = class {
2858
2867
  if (this.reentrancyDepth > 0) return;
2859
2868
  this.reentrancyDepth += 1;
2860
2869
  try {
2861
- this.ctx.emitTelemetry({
2870
+ const payload = {
2862
2871
  contract_id: result.contractId,
2863
2872
  sdk_version: this.ctx.sdkVersion,
2864
2873
  sdk_platform: "web",
@@ -2866,7 +2875,13 @@ var VerifierReporter = class {
2866
2875
  run_context: this.ctx.runContext,
2867
2876
  run_id: this.ctx.runId,
2868
2877
  verification_phase: phase
2869
- });
2878
+ };
2879
+ if (this.verifiers) {
2880
+ runOnReportContractFailure(this.verifiers, this, this.ctx, {
2881
+ outgoingPayload: payload
2882
+ });
2883
+ }
2884
+ this.ctx.emitTelemetry(payload);
2870
2885
  } finally {
2871
2886
  this.reentrancyDepth -= 1;
2872
2887
  }
@@ -3156,9 +3171,31 @@ var VERIFIER_FLUSH_INTERVAL_PARITY = {
3156
3171
  "eventFlushIntervalMs default = 2000ms (Web/Node/RN/Swift/Android parity)",
3157
3172
  nowMs() - t0
3158
3173
  );
3174
+ },
3175
+ // Hot-path hook — on every real track() we re-affirm the LIVE configured
3176
+ // flush interval (carried on the observation), so the parity guarantee is
3177
+ // verified against the running SDK in the customer flow, not just a
3178
+ // canonical constant at boot. It can't change per-event, but observing it
3179
+ // per-event is what makes the contract genuinely runtime-verified.
3180
+ hooks: {
3181
+ onTrack(obs) {
3182
+ const t0 = nowMs();
3183
+ const CANONICAL_DEFAULT_MS = 2e3;
3184
+ const ms = obs.flushIntervalMs;
3185
+ if (typeof ms !== "number" || ms < 100 || ms > 6e4) {
3186
+ return fail(
3187
+ "flush-interval-parity",
3188
+ `live eventFlushIntervalMs=${ms} outside reasonable bounds [100, 60000]`,
3189
+ nowMs() - t0
3190
+ );
3191
+ }
3192
+ return pass(
3193
+ "flush-interval-parity",
3194
+ ms === CANONICAL_DEFAULT_MS ? "live eventFlushIntervalMs = 2000ms (canonical parity value)" : `live eventFlushIntervalMs = ${ms}ms (permitted override; canonical default 2000ms)`,
3195
+ nowMs() - t0
3196
+ );
3197
+ }
3159
3198
  }
3160
- // No hot-path hook — the flush interval is set once at start() and
3161
- // never changes per-operation.
3162
3199
  };
3163
3200
  function buildFlushIntervalVerifier(configuredIntervalMs) {
3164
3201
  return {
@@ -3285,6 +3322,26 @@ var CONTRACT_FAILED_FORBIDDEN_FIELDS = [
3285
3322
  "referrer",
3286
3323
  "deviceId"
3287
3324
  ];
3325
+ function contractFailedSchemaViolation(keys) {
3326
+ const allowed = /* @__PURE__ */ new Set([
3327
+ ...CONTRACT_FAILED_REQUIRED_FIELDS,
3328
+ ...CONTRACT_FAILED_OPTIONAL_FIELDS
3329
+ ]);
3330
+ const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
3331
+ for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
3332
+ if (!keys.includes(required)) return `missing required field: ${required}`;
3333
+ }
3334
+ for (const k of keys) {
3335
+ if (forbidden.has(k)) return `forbidden field on wire: ${k}`;
3336
+ }
3337
+ for (const k of keys) {
3338
+ if (!allowed.has(k)) {
3339
+ return `unrecognised field on wire: ${k} (not in required \u222A optional)`;
3340
+ }
3341
+ }
3342
+ return null;
3343
+ }
3344
+ var CONTRACT_FAILED_OK_EVIDENCE = `fields \u2286 required(${CONTRACT_FAILED_REQUIRED_FIELDS.length}) \u222A optional(${CONTRACT_FAILED_OPTIONAL_FIELDS.length}); ${CONTRACT_FAILED_FORBIDDEN_FIELDS.length} forbidden absent`;
3288
3345
  var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
3289
3346
  contractId: "contract-failed-payload-schema-lock",
3290
3347
  bootTest() {
@@ -3299,43 +3356,35 @@ var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
3299
3356
  verification_phase: "boot"
3300
3357
  };
3301
3358
  const keys = Object.keys(syntheticPayload);
3302
- const allowed = /* @__PURE__ */ new Set([
3303
- ...CONTRACT_FAILED_REQUIRED_FIELDS,
3304
- ...CONTRACT_FAILED_OPTIONAL_FIELDS
3305
- ]);
3306
- const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
3307
- for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
3308
- if (!keys.includes(required)) {
3309
- return fail(
3310
- "contract-failed-payload-schema-lock",
3311
- `missing required field: ${required}`,
3312
- nowMs() - t0
3313
- );
3314
- }
3315
- }
3316
- for (const k of keys) {
3317
- if (forbidden.has(k)) {
3318
- return fail(
3319
- "contract-failed-payload-schema-lock",
3320
- `forbidden field on wire: ${k}`,
3321
- nowMs() - t0
3322
- );
3323
- }
3324
- }
3325
- for (const k of keys) {
3326
- if (!allowed.has(k)) {
3327
- return fail(
3328
- "contract-failed-payload-schema-lock",
3329
- `unrecognised field on wire: ${k} (not in required \u222A optional)`,
3330
- nowMs() - t0
3331
- );
3332
- }
3359
+ const violation = contractFailedSchemaViolation(keys);
3360
+ if (violation) {
3361
+ return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
3333
3362
  }
3334
3363
  return pass(
3335
3364
  "contract-failed-payload-schema-lock",
3336
- `${keys.length} fields \u2286 required(${CONTRACT_FAILED_REQUIRED_FIELDS.length}) \u222A optional(${CONTRACT_FAILED_OPTIONAL_FIELDS.length}); ${CONTRACT_FAILED_FORBIDDEN_FIELDS.length} forbidden absent`,
3365
+ `${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
3337
3366
  nowMs() - t0
3338
3367
  );
3368
+ },
3369
+ // Hot-path hook — fires on the REAL reportFail emit (VerifierReporter,
3370
+ // inside the re-entrancy guard) against the exact payload about to go on
3371
+ // the wire. This is the assertion the guard at reportFail was built in
3372
+ // anticipation of: every reliability-channel write is schema-locked in the
3373
+ // field, against real data, not just the synthetic boot mirror.
3374
+ hooks: {
3375
+ onReportContractFailure(obs) {
3376
+ const t0 = nowMs();
3377
+ const keys = Object.keys(obs.outgoingPayload);
3378
+ const violation = contractFailedSchemaViolation(keys);
3379
+ if (violation) {
3380
+ return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
3381
+ }
3382
+ return pass(
3383
+ "contract-failed-payload-schema-lock",
3384
+ `${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
3385
+ nowMs() - t0
3386
+ );
3387
+ }
3339
3388
  }
3340
3389
  };
3341
3390
  var BACKEND_WIRE_CODES = Object.freeze([
@@ -3386,6 +3435,39 @@ var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
3386
3435
  nowMs() - t0
3387
3436
  );
3388
3437
  }
3438
+ },
3439
+ // Hot-path hook — when the SDK parses a real wire error, assert the code
3440
+ // the customer actually hit carries usable remediation in the shipped
3441
+ // catalogue. Scoped to BACKEND_WIRE_CODES (the contract's set); other
3442
+ // codes are out of scope and pass. This turns the catalogue from a
3443
+ // boot-only completeness claim into a per-error field assertion: a backend
3444
+ // code that ships without remediation is caught the first time a customer
3445
+ // hits it, not only if the off-by-default boot self-test ran.
3446
+ hooks: {
3447
+ onErrorParse(obs) {
3448
+ const t0 = nowMs();
3449
+ const code = obs.errorCode;
3450
+ if (!code || !BACKEND_WIRE_CODES.includes(code)) {
3451
+ return pass(
3452
+ "sdk-error-codes-catalogue",
3453
+ `wire code "${code || "(none)"}" is out of the backend-catalogue scope`,
3454
+ nowMs() - t0
3455
+ );
3456
+ }
3457
+ const entry = getErrorCode(code);
3458
+ if (!entry || !entry.description || entry.description.trim().length === 0 || !entry.resolution || entry.resolution.trim().length === 0) {
3459
+ return fail(
3460
+ "sdk-error-codes-catalogue",
3461
+ `backend wire code "${code}" hit in the field has no description+resolution in the shipped catalogue`,
3462
+ nowMs() - t0
3463
+ );
3464
+ }
3465
+ return pass(
3466
+ "sdk-error-codes-catalogue",
3467
+ `backend wire code "${code}" carries description + resolution`,
3468
+ nowMs() - t0
3469
+ );
3470
+ }
3389
3471
  }
3390
3472
  };
3391
3473
  var STATIC_VERIFIERS = Object.freeze([
@@ -3509,6 +3591,24 @@ function runOnErrorParse(verifiers, reporter, ctx, obs) {
3509
3591
  reporter.report(result, "hot_path", "errorParse");
3510
3592
  }
3511
3593
  }
3594
+ function runOnReportContractFailure(verifiers, reporter, ctx, obs) {
3595
+ if (ctx.disableContractAssertions) return;
3596
+ for (const verifier of verifiers) {
3597
+ const hook = verifier.hooks?.onReportContractFailure;
3598
+ if (!hook) continue;
3599
+ let result;
3600
+ try {
3601
+ result = hook(obs);
3602
+ } catch (err) {
3603
+ result = fail(
3604
+ verifier.contractId,
3605
+ `hook threw: ${err.message?.slice(0, 80) ?? "unknown"}`,
3606
+ 0
3607
+ );
3608
+ }
3609
+ reporter.report(result, "hot_path", "reportContractFailure");
3610
+ }
3611
+ }
3512
3612
  function defaultDebugModeFlag() {
3513
3613
  try {
3514
3614
  if (typeof process !== "undefined" && process.env) {
@@ -4341,6 +4441,9 @@ var CrossdeckClient = class {
4341
4441
  this.verifiers = null;
4342
4442
  this.verifierReporter = null;
4343
4443
  this.verifierCtx = null;
4444
+ // The live configured event-flush interval, surfaced to the track-path
4445
+ // verifier so flush-interval-parity validates the real cadence per event.
4446
+ this.flushIntervalMs = 2e3;
4344
4447
  }
4345
4448
  /**
4346
4449
  * Boot the SDK. Idempotent — calling init twice with the same options
@@ -4620,6 +4723,8 @@ var CrossdeckClient = class {
4620
4723
  this.verifierReporter = new VerifierReporter(this.verifierCtx);
4621
4724
  const flushVerifier = buildFlushIntervalVerifier(opts.eventFlushIntervalMs);
4622
4725
  this.verifiers = Object.freeze([...STATIC_VERIFIERS, flushVerifier]);
4726
+ this.flushIntervalMs = opts.eventFlushIntervalMs;
4727
+ this.verifierReporter.attachVerifiers(this.verifiers);
4623
4728
  if (localDevMode) {
4624
4729
  const verifyAtBootLocal = options.verifyContractsAtBoot ?? debugDefault;
4625
4730
  if (verifyAtBootLocal && this.verifierReporter) {
@@ -5194,9 +5299,7 @@ var CrossdeckClient = class {
5194
5299
  }
5195
5300
  }
5196
5301
  const supers = s.superProps.getSuperProperties();
5197
- for (const k of Object.keys(supers)) {
5198
- if (!(k in enriched)) enriched[k] = supers[k];
5199
- }
5302
+ Object.assign(enriched, supers);
5200
5303
  const groupIds = s.superProps.getGroupIds();
5201
5304
  if (Object.keys(groupIds).length > 0) {
5202
5305
  enriched.$groups = groupIds;
@@ -5216,7 +5319,8 @@ var CrossdeckClient = class {
5216
5319
  callerProperties: validation.properties,
5217
5320
  superProperties: supers,
5218
5321
  deviceProperties: s.deviceInfo,
5219
- mergedProperties: enriched
5322
+ mergedProperties: enriched,
5323
+ flushIntervalMs: this.flushIntervalMs
5220
5324
  });
5221
5325
  }
5222
5326
  if (!isError && !isWebVital) {
@@ -5561,8 +5665,8 @@ function installUnloadFlush(onUnload) {
5561
5665
  }
5562
5666
 
5563
5667
  // src/_contracts-bundled.ts
5564
- var BUNDLED_IN = "@cross-deck/web@1.6.3";
5565
- var SDK_VERSION2 = "1.6.3";
5668
+ var BUNDLED_IN = "@cross-deck/web@1.6.4";
5669
+ var SDK_VERSION2 = "1.6.4";
5566
5670
  var BUNDLED_CONTRACTS = Object.freeze([
5567
5671
  {
5568
5672
  "id": "contract-failed-payload-schema-lock",
@@ -5684,7 +5788,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5684
5788
  "legal/security/index.html#diagnostic",
5685
5789
  "legal/sdk-data/index.html#b-diagnostic"
5686
5790
  ],
5687
- "bundledIn": "@cross-deck/web@1.6.3",
5791
+ "bundledIn": "@cross-deck/web@1.6.4",
5688
5792
  "runtimeVerified": true
5689
5793
  },
5690
5794
  {
@@ -5724,7 +5828,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5724
5828
  ],
5725
5829
  "registeredAt": "2026-05-26",
5726
5830
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
5727
- "bundledIn": "@cross-deck/web@1.6.3",
5831
+ "bundledIn": "@cross-deck/web@1.6.4",
5728
5832
  "runtimeVerified": true
5729
5833
  },
5730
5834
  {
@@ -5770,7 +5874,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5770
5874
  ],
5771
5875
  "registeredAt": "2026-05-26",
5772
5876
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
5773
- "bundledIn": "@cross-deck/web@1.6.3",
5877
+ "bundledIn": "@cross-deck/web@1.6.4",
5774
5878
  "runtimeVerified": true
5775
5879
  },
5776
5880
  {
@@ -5876,7 +5980,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5876
5980
  ],
5877
5981
  "registeredAt": "2026-05-26",
5878
5982
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
5879
- "bundledIn": "@cross-deck/web@1.6.3",
5983
+ "bundledIn": "@cross-deck/web@1.6.4",
5880
5984
  "runtimeVerified": true
5881
5985
  },
5882
5986
  {
@@ -5904,7 +6008,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5904
6008
  ],
5905
6009
  "registeredAt": "2026-05-26",
5906
6010
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.5",
5907
- "bundledIn": "@cross-deck/web@1.6.3",
6011
+ "bundledIn": "@cross-deck/web@1.6.4",
5908
6012
  "runtimeVerified": false
5909
6013
  },
5910
6014
  {
@@ -5984,7 +6088,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5984
6088
  ],
5985
6089
  "registeredAt": "2026-05-26",
5986
6090
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
5987
- "bundledIn": "@cross-deck/web@1.6.3",
6091
+ "bundledIn": "@cross-deck/web@1.6.4",
5988
6092
  "runtimeVerified": true
5989
6093
  },
5990
6094
  {
@@ -6030,7 +6134,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6030
6134
  ],
6031
6135
  "registeredAt": "2026-05-26",
6032
6136
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
6033
- "bundledIn": "@cross-deck/web@1.6.3",
6137
+ "bundledIn": "@cross-deck/web@1.6.4",
6034
6138
  "runtimeVerified": true
6035
6139
  },
6036
6140
  {
@@ -6072,7 +6176,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6072
6176
  ],
6073
6177
  "registeredAt": "2026-05-26",
6074
6178
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.2",
6075
- "bundledIn": "@cross-deck/web@1.6.3",
6179
+ "bundledIn": "@cross-deck/web@1.6.4",
6076
6180
  "runtimeVerified": true
6077
6181
  },
6078
6182
  {
@@ -6106,7 +6210,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6106
6210
  ],
6107
6211
  "registeredAt": "2026-05-26",
6108
6212
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
6109
- "bundledIn": "@cross-deck/web@1.6.3",
6213
+ "bundledIn": "@cross-deck/web@1.6.4",
6110
6214
  "runtimeVerified": false
6111
6215
  }
6112
6216
  ]);