@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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "generatedAt": "2026-06-01T07:38:09.406Z",
3
+ "generatedAt": "2026-06-01T09:52:17.794Z",
4
4
  "sdk": "@cross-deck/web",
5
5
  "codes": [
6
6
  {
package/dist/index.cjs CHANGED
@@ -99,7 +99,7 @@ function typeMapForStatus(status) {
99
99
  }
100
100
 
101
101
  // src/_version.ts
102
- var SDK_VERSION = "1.6.3";
102
+ var SDK_VERSION = "1.6.4";
103
103
  var SDK_NAME = "@cross-deck/web";
104
104
 
105
105
  // src/http.ts
@@ -2865,6 +2865,15 @@ var VerifierReporter = class {
2865
2865
  constructor(ctx) {
2866
2866
  this.ctx = ctx;
2867
2867
  this.reentrancyDepth = 0;
2868
+ /** The live verifier set — attached by the SDK right after init so the
2869
+ * emit path can schema-lock its OWN outgoing telemetry against the real
2870
+ * payload. Null until attached (boot-time reports still work without it). */
2871
+ this.verifiers = null;
2872
+ }
2873
+ /** Hand the reporter the live verifier set so `reportFail` can run the
2874
+ * `onReportContractFailure` hook on the exact payload it's about to emit. */
2875
+ attachVerifiers(verifiers) {
2876
+ this.verifiers = verifiers;
2868
2877
  }
2869
2878
  /**
2870
2879
  * Report a verifier result. Pass `operation` for hot-path results
@@ -2893,7 +2902,7 @@ var VerifierReporter = class {
2893
2902
  if (this.reentrancyDepth > 0) return;
2894
2903
  this.reentrancyDepth += 1;
2895
2904
  try {
2896
- this.ctx.emitTelemetry({
2905
+ const payload = {
2897
2906
  contract_id: result.contractId,
2898
2907
  sdk_version: this.ctx.sdkVersion,
2899
2908
  sdk_platform: "web",
@@ -2901,7 +2910,13 @@ var VerifierReporter = class {
2901
2910
  run_context: this.ctx.runContext,
2902
2911
  run_id: this.ctx.runId,
2903
2912
  verification_phase: phase
2904
- });
2913
+ };
2914
+ if (this.verifiers) {
2915
+ runOnReportContractFailure(this.verifiers, this, this.ctx, {
2916
+ outgoingPayload: payload
2917
+ });
2918
+ }
2919
+ this.ctx.emitTelemetry(payload);
2905
2920
  } finally {
2906
2921
  this.reentrancyDepth -= 1;
2907
2922
  }
@@ -3191,9 +3206,31 @@ var VERIFIER_FLUSH_INTERVAL_PARITY = {
3191
3206
  "eventFlushIntervalMs default = 2000ms (Web/Node/RN/Swift/Android parity)",
3192
3207
  nowMs() - t0
3193
3208
  );
3209
+ },
3210
+ // Hot-path hook — on every real track() we re-affirm the LIVE configured
3211
+ // flush interval (carried on the observation), so the parity guarantee is
3212
+ // verified against the running SDK in the customer flow, not just a
3213
+ // canonical constant at boot. It can't change per-event, but observing it
3214
+ // per-event is what makes the contract genuinely runtime-verified.
3215
+ hooks: {
3216
+ onTrack(obs) {
3217
+ const t0 = nowMs();
3218
+ const CANONICAL_DEFAULT_MS = 2e3;
3219
+ const ms = obs.flushIntervalMs;
3220
+ if (typeof ms !== "number" || ms < 100 || ms > 6e4) {
3221
+ return fail(
3222
+ "flush-interval-parity",
3223
+ `live eventFlushIntervalMs=${ms} outside reasonable bounds [100, 60000]`,
3224
+ nowMs() - t0
3225
+ );
3226
+ }
3227
+ return pass(
3228
+ "flush-interval-parity",
3229
+ ms === CANONICAL_DEFAULT_MS ? "live eventFlushIntervalMs = 2000ms (canonical parity value)" : `live eventFlushIntervalMs = ${ms}ms (permitted override; canonical default 2000ms)`,
3230
+ nowMs() - t0
3231
+ );
3232
+ }
3194
3233
  }
3195
- // No hot-path hook — the flush interval is set once at start() and
3196
- // never changes per-operation.
3197
3234
  };
3198
3235
  function buildFlushIntervalVerifier(configuredIntervalMs) {
3199
3236
  return {
@@ -3320,6 +3357,26 @@ var CONTRACT_FAILED_FORBIDDEN_FIELDS = [
3320
3357
  "referrer",
3321
3358
  "deviceId"
3322
3359
  ];
3360
+ function contractFailedSchemaViolation(keys) {
3361
+ const allowed = /* @__PURE__ */ new Set([
3362
+ ...CONTRACT_FAILED_REQUIRED_FIELDS,
3363
+ ...CONTRACT_FAILED_OPTIONAL_FIELDS
3364
+ ]);
3365
+ const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
3366
+ for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
3367
+ if (!keys.includes(required)) return `missing required field: ${required}`;
3368
+ }
3369
+ for (const k of keys) {
3370
+ if (forbidden.has(k)) return `forbidden field on wire: ${k}`;
3371
+ }
3372
+ for (const k of keys) {
3373
+ if (!allowed.has(k)) {
3374
+ return `unrecognised field on wire: ${k} (not in required \u222A optional)`;
3375
+ }
3376
+ }
3377
+ return null;
3378
+ }
3379
+ 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`;
3323
3380
  var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
3324
3381
  contractId: "contract-failed-payload-schema-lock",
3325
3382
  bootTest() {
@@ -3334,43 +3391,35 @@ var VERIFIER_CONTRACT_FAILED_PAYLOAD_SCHEMA_LOCK = {
3334
3391
  verification_phase: "boot"
3335
3392
  };
3336
3393
  const keys = Object.keys(syntheticPayload);
3337
- const allowed = /* @__PURE__ */ new Set([
3338
- ...CONTRACT_FAILED_REQUIRED_FIELDS,
3339
- ...CONTRACT_FAILED_OPTIONAL_FIELDS
3340
- ]);
3341
- const forbidden = new Set(CONTRACT_FAILED_FORBIDDEN_FIELDS);
3342
- for (const required of CONTRACT_FAILED_REQUIRED_FIELDS) {
3343
- if (!keys.includes(required)) {
3344
- return fail(
3345
- "contract-failed-payload-schema-lock",
3346
- `missing required field: ${required}`,
3347
- nowMs() - t0
3348
- );
3349
- }
3350
- }
3351
- for (const k of keys) {
3352
- if (forbidden.has(k)) {
3353
- return fail(
3354
- "contract-failed-payload-schema-lock",
3355
- `forbidden field on wire: ${k}`,
3356
- nowMs() - t0
3357
- );
3358
- }
3359
- }
3360
- for (const k of keys) {
3361
- if (!allowed.has(k)) {
3362
- return fail(
3363
- "contract-failed-payload-schema-lock",
3364
- `unrecognised field on wire: ${k} (not in required \u222A optional)`,
3365
- nowMs() - t0
3366
- );
3367
- }
3394
+ const violation = contractFailedSchemaViolation(keys);
3395
+ if (violation) {
3396
+ return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
3368
3397
  }
3369
3398
  return pass(
3370
3399
  "contract-failed-payload-schema-lock",
3371
- `${keys.length} fields \u2286 required(${CONTRACT_FAILED_REQUIRED_FIELDS.length}) \u222A optional(${CONTRACT_FAILED_OPTIONAL_FIELDS.length}); ${CONTRACT_FAILED_FORBIDDEN_FIELDS.length} forbidden absent`,
3400
+ `${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
3372
3401
  nowMs() - t0
3373
3402
  );
3403
+ },
3404
+ // Hot-path hook — fires on the REAL reportFail emit (VerifierReporter,
3405
+ // inside the re-entrancy guard) against the exact payload about to go on
3406
+ // the wire. This is the assertion the guard at reportFail was built in
3407
+ // anticipation of: every reliability-channel write is schema-locked in the
3408
+ // field, against real data, not just the synthetic boot mirror.
3409
+ hooks: {
3410
+ onReportContractFailure(obs) {
3411
+ const t0 = nowMs();
3412
+ const keys = Object.keys(obs.outgoingPayload);
3413
+ const violation = contractFailedSchemaViolation(keys);
3414
+ if (violation) {
3415
+ return fail("contract-failed-payload-schema-lock", violation, nowMs() - t0);
3416
+ }
3417
+ return pass(
3418
+ "contract-failed-payload-schema-lock",
3419
+ `${keys.length} ${CONTRACT_FAILED_OK_EVIDENCE}`,
3420
+ nowMs() - t0
3421
+ );
3422
+ }
3374
3423
  }
3375
3424
  };
3376
3425
  var BACKEND_WIRE_CODES = Object.freeze([
@@ -3421,6 +3470,39 @@ var VERIFIER_SDK_ERROR_CODES_CATALOGUE = {
3421
3470
  nowMs() - t0
3422
3471
  );
3423
3472
  }
3473
+ },
3474
+ // Hot-path hook — when the SDK parses a real wire error, assert the code
3475
+ // the customer actually hit carries usable remediation in the shipped
3476
+ // catalogue. Scoped to BACKEND_WIRE_CODES (the contract's set); other
3477
+ // codes are out of scope and pass. This turns the catalogue from a
3478
+ // boot-only completeness claim into a per-error field assertion: a backend
3479
+ // code that ships without remediation is caught the first time a customer
3480
+ // hits it, not only if the off-by-default boot self-test ran.
3481
+ hooks: {
3482
+ onErrorParse(obs) {
3483
+ const t0 = nowMs();
3484
+ const code = obs.errorCode;
3485
+ if (!code || !BACKEND_WIRE_CODES.includes(code)) {
3486
+ return pass(
3487
+ "sdk-error-codes-catalogue",
3488
+ `wire code "${code || "(none)"}" is out of the backend-catalogue scope`,
3489
+ nowMs() - t0
3490
+ );
3491
+ }
3492
+ const entry = getErrorCode(code);
3493
+ if (!entry || !entry.description || entry.description.trim().length === 0 || !entry.resolution || entry.resolution.trim().length === 0) {
3494
+ return fail(
3495
+ "sdk-error-codes-catalogue",
3496
+ `backend wire code "${code}" hit in the field has no description+resolution in the shipped catalogue`,
3497
+ nowMs() - t0
3498
+ );
3499
+ }
3500
+ return pass(
3501
+ "sdk-error-codes-catalogue",
3502
+ `backend wire code "${code}" carries description + resolution`,
3503
+ nowMs() - t0
3504
+ );
3505
+ }
3424
3506
  }
3425
3507
  };
3426
3508
  var STATIC_VERIFIERS = Object.freeze([
@@ -3544,6 +3626,24 @@ function runOnErrorParse(verifiers, reporter, ctx, obs) {
3544
3626
  reporter.report(result, "hot_path", "errorParse");
3545
3627
  }
3546
3628
  }
3629
+ function runOnReportContractFailure(verifiers, reporter, ctx, obs) {
3630
+ if (ctx.disableContractAssertions) return;
3631
+ for (const verifier of verifiers) {
3632
+ const hook = verifier.hooks?.onReportContractFailure;
3633
+ if (!hook) continue;
3634
+ let result;
3635
+ try {
3636
+ result = hook(obs);
3637
+ } catch (err) {
3638
+ result = fail(
3639
+ verifier.contractId,
3640
+ `hook threw: ${err.message?.slice(0, 80) ?? "unknown"}`,
3641
+ 0
3642
+ );
3643
+ }
3644
+ reporter.report(result, "hot_path", "reportContractFailure");
3645
+ }
3646
+ }
3547
3647
  function defaultDebugModeFlag() {
3548
3648
  try {
3549
3649
  if (typeof process !== "undefined" && process.env) {
@@ -4376,6 +4476,9 @@ var CrossdeckClient = class {
4376
4476
  this.verifiers = null;
4377
4477
  this.verifierReporter = null;
4378
4478
  this.verifierCtx = null;
4479
+ // The live configured event-flush interval, surfaced to the track-path
4480
+ // verifier so flush-interval-parity validates the real cadence per event.
4481
+ this.flushIntervalMs = 2e3;
4379
4482
  }
4380
4483
  /**
4381
4484
  * Boot the SDK. Idempotent — calling init twice with the same options
@@ -4655,6 +4758,8 @@ var CrossdeckClient = class {
4655
4758
  this.verifierReporter = new VerifierReporter(this.verifierCtx);
4656
4759
  const flushVerifier = buildFlushIntervalVerifier(opts.eventFlushIntervalMs);
4657
4760
  this.verifiers = Object.freeze([...STATIC_VERIFIERS, flushVerifier]);
4761
+ this.flushIntervalMs = opts.eventFlushIntervalMs;
4762
+ this.verifierReporter.attachVerifiers(this.verifiers);
4658
4763
  if (localDevMode) {
4659
4764
  const verifyAtBootLocal = options.verifyContractsAtBoot ?? debugDefault;
4660
4765
  if (verifyAtBootLocal && this.verifierReporter) {
@@ -5229,9 +5334,7 @@ var CrossdeckClient = class {
5229
5334
  }
5230
5335
  }
5231
5336
  const supers = s.superProps.getSuperProperties();
5232
- for (const k of Object.keys(supers)) {
5233
- if (!(k in enriched)) enriched[k] = supers[k];
5234
- }
5337
+ Object.assign(enriched, supers);
5235
5338
  const groupIds = s.superProps.getGroupIds();
5236
5339
  if (Object.keys(groupIds).length > 0) {
5237
5340
  enriched.$groups = groupIds;
@@ -5251,7 +5354,8 @@ var CrossdeckClient = class {
5251
5354
  callerProperties: validation.properties,
5252
5355
  superProperties: supers,
5253
5356
  deviceProperties: s.deviceInfo,
5254
- mergedProperties: enriched
5357
+ mergedProperties: enriched,
5358
+ flushIntervalMs: this.flushIntervalMs
5255
5359
  });
5256
5360
  }
5257
5361
  if (!isError && !isWebVital) {
@@ -5596,8 +5700,8 @@ function installUnloadFlush(onUnload) {
5596
5700
  }
5597
5701
 
5598
5702
  // src/_contracts-bundled.ts
5599
- var BUNDLED_IN = "@cross-deck/web@1.6.3";
5600
- var SDK_VERSION2 = "1.6.3";
5703
+ var BUNDLED_IN = "@cross-deck/web@1.6.4";
5704
+ var SDK_VERSION2 = "1.6.4";
5601
5705
  var BUNDLED_CONTRACTS = Object.freeze([
5602
5706
  {
5603
5707
  "id": "contract-failed-payload-schema-lock",
@@ -5719,7 +5823,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5719
5823
  "legal/security/index.html#diagnostic",
5720
5824
  "legal/sdk-data/index.html#b-diagnostic"
5721
5825
  ],
5722
- "bundledIn": "@cross-deck/web@1.6.3",
5826
+ "bundledIn": "@cross-deck/web@1.6.4",
5723
5827
  "runtimeVerified": true
5724
5828
  },
5725
5829
  {
@@ -5759,7 +5863,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5759
5863
  ],
5760
5864
  "registeredAt": "2026-05-26",
5761
5865
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 8 (codifies existing contract)",
5762
- "bundledIn": "@cross-deck/web@1.6.3",
5866
+ "bundledIn": "@cross-deck/web@1.6.4",
5763
5867
  "runtimeVerified": true
5764
5868
  },
5765
5869
  {
@@ -5805,7 +5909,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5805
5909
  ],
5806
5910
  "registeredAt": "2026-05-26",
5807
5911
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.3",
5808
- "bundledIn": "@cross-deck/web@1.6.3",
5912
+ "bundledIn": "@cross-deck/web@1.6.4",
5809
5913
  "runtimeVerified": true
5810
5914
  },
5811
5915
  {
@@ -5911,7 +6015,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5911
6015
  ],
5912
6016
  "registeredAt": "2026-05-26",
5913
6017
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 2.2.a + 2.2.b + 2.2.c",
5914
- "bundledIn": "@cross-deck/web@1.6.3",
6018
+ "bundledIn": "@cross-deck/web@1.6.4",
5915
6019
  "runtimeVerified": true
5916
6020
  },
5917
6021
  {
@@ -5939,7 +6043,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
5939
6043
  ],
5940
6044
  "registeredAt": "2026-05-26",
5941
6045
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 5.5",
5942
- "bundledIn": "@cross-deck/web@1.6.3",
6046
+ "bundledIn": "@cross-deck/web@1.6.4",
5943
6047
  "runtimeVerified": false
5944
6048
  },
5945
6049
  {
@@ -6019,7 +6123,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6019
6123
  ],
6020
6124
  "registeredAt": "2026-05-26",
6021
6125
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 1.3 (web/RN) + dogfood-gap fix (swift + android)",
6022
- "bundledIn": "@cross-deck/web@1.6.3",
6126
+ "bundledIn": "@cross-deck/web@1.6.4",
6023
6127
  "runtimeVerified": true
6024
6128
  },
6025
6129
  {
@@ -6065,7 +6169,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6065
6169
  ],
6066
6170
  "registeredAt": "2026-05-26",
6067
6171
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 6.2",
6068
- "bundledIn": "@cross-deck/web@1.6.3",
6172
+ "bundledIn": "@cross-deck/web@1.6.4",
6069
6173
  "runtimeVerified": true
6070
6174
  },
6071
6175
  {
@@ -6107,7 +6211,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6107
6211
  ],
6108
6212
  "registeredAt": "2026-05-26",
6109
6213
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.2",
6110
- "bundledIn": "@cross-deck/web@1.6.3",
6214
+ "bundledIn": "@cross-deck/web@1.6.4",
6111
6215
  "runtimeVerified": true
6112
6216
  },
6113
6217
  {
@@ -6141,7 +6245,7 @@ var BUNDLED_CONTRACTS = Object.freeze([
6141
6245
  ],
6142
6246
  "registeredAt": "2026-05-26",
6143
6247
  "firstRegisteredIn": "bank-grade reconciliation v1.4.0 \u2014 phase 3.5",
6144
- "bundledIn": "@cross-deck/web@1.6.3",
6248
+ "bundledIn": "@cross-deck/web@1.6.4",
6145
6249
  "runtimeVerified": false
6146
6250
  }
6147
6251
  ]);