blockintel-gate-sdk 0.3.6 → 0.3.7

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/README.md CHANGED
@@ -13,6 +13,12 @@ npm install @blockintel/gate-sdk
13
13
  - Node.js >= 18.0.0 (uses global `fetch` API)
14
14
  - TypeScript >= 5.0.0 (optional, for type definitions)
15
15
 
16
+ ### Hot Path compatibility
17
+
18
+ - **Mode**: Default is `SHADOW` (Hot Path returns ALLOW with reason codes for would-block decisions). Set `mode: 'ENFORCE'` or `GATE_MODE=ENFORCE` for real BLOCK responses.
19
+ - **signingContext**: Hot Path requires `actorPrincipal` and `signerId`. The SDK defaults them when missing (`gate-sdk-client` or from `signingContext.signerId`).
20
+ - **ESM**: HMAC and SHA-256 use `node:crypto` (no `require('crypto')`), so the SDK works in ESM (`"type": "module"`) and in bundled canary apps.
21
+
16
22
  ## Quick Start
17
23
 
18
24
  ### HMAC Authentication
@@ -203,6 +209,9 @@ When step-up is disabled, the SDK treats `REQUIRE_STEP_UP` as `BLOCK` by default
203
209
  GATE_BASE_URL=https://gate.blockintelai.com
204
210
  GATE_TENANT_ID=your-tenant-id
205
211
 
212
+ # Heartbeat (required when not using local mode; parity with Python GATE_HEARTBEAT_KEY)
213
+ GATE_HEARTBEAT_KEY=your-heartbeat-key
214
+
206
215
  # HMAC Authentication
207
216
  GATE_KEY_ID=your-key-id
208
217
  GATE_HMAC_SECRET=your-secret
@@ -376,6 +385,21 @@ The SDK automatically retries failed requests:
376
385
  - Same `requestId` is used across all retries
377
386
  - Ensures idempotency on Gate server
378
387
 
388
+ ## Degraded Mode / X-BlockIntel-Degraded
389
+
390
+ When the SDK is in a degraded situation, it logs `X-BlockIntel-Degraded: true` with a `reason` for **logs and telemetry only**. This is **never sent as an HTTP request header** to the Gate server.
391
+
392
+ **Reasons:** `retry`, `429`, `fail_open`, `fail_safe_allow`.
393
+
394
+ **Example (one line):**
395
+ `[GATE SDK] X-BlockIntel-Degraded: true (reason=retry) attempt=1/3 status=503 err=RATE_LIMITED`
396
+
397
+ **How to observe:**
398
+ - **Logs:** `[GATE SDK] X-BlockIntel-Degraded: true (reason: <reason>)` via `console.warn`. Pipe stderr to your log aggregator.
399
+ - **Metrics:** Use `onMetrics`; metrics include `timeouts`, `errors`, `failOpen`, etc. Correlate with log lines if you ship both.
400
+
401
+ **Manual check (retry):** Point the SDK at an endpoint that returns 5xx; confirm one degraded log per retry attempt including `attempt`, `max`, and `status`/`err`.
402
+
379
403
  ## Heartbeat System
380
404
 
381
405
  The SDK includes a **Heartbeat Manager** that automatically acquires and refreshes heartbeat tokens from the Gate Control Plane. Heartbeat tokens are required for all signing operations and ensure that Gate is alive and enforcing policy.
package/dist/index.cjs CHANGED
@@ -2,18 +2,12 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var crypto = require('crypto');
5
6
  var uuid = require('uuid');
6
7
  var clientKms = require('@aws-sdk/client-kms');
7
- var crypto$1 = require('crypto');
8
8
 
9
9
  var __defProp = Object.defineProperty;
10
10
  var __getOwnPropNames = Object.getOwnPropertyNames;
11
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
12
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
13
- }) : x)(function(x) {
14
- if (typeof require !== "undefined") return require.apply(this, arguments);
15
- throw Error('Dynamic require of "' + x + '" is not supported');
16
- });
17
11
  var __esm = (fn, res) => function __init() {
18
12
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
19
13
  };
@@ -50,56 +44,24 @@ function canonicalizeJson(obj) {
50
44
  return JSON.stringify(sorted);
51
45
  }
52
46
  async function sha256Hex(input) {
53
- if (typeof crypto !== "undefined" && crypto.subtle) {
54
- const encoder = new TextEncoder();
55
- const data = encoder.encode(input);
56
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
57
- const hashArray = Array.from(new Uint8Array(hashBuffer));
58
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
59
- }
60
- if (typeof __require !== "undefined") {
61
- const crypto2 = __require("crypto");
62
- return crypto2.createHash("sha256").update(input, "utf8").digest("hex");
63
- }
64
- throw new Error("SHA-256 not available in this environment");
47
+ return crypto.createHash("sha256").update(input, "utf8").digest("hex");
65
48
  }
66
49
  var init_canonicalJson = __esm({
67
50
  "src/utils/canonicalJson.ts"() {
68
51
  }
69
52
  });
70
-
71
- // src/utils/crypto.ts
72
53
  async function hmacSha256(secret, message) {
73
- if (typeof __require !== "undefined") {
74
- const crypto2 = __require("crypto");
75
- const hmac = crypto2.createHmac("sha256", secret);
76
- hmac.update(message, "utf8");
77
- const signatureHex = hmac.digest("hex");
78
- console.error("[HMAC CRYPTO DEBUG] Signature computation:", JSON.stringify({
79
- secretLength: secret.length,
80
- messageLength: message.length,
81
- messagePreview: message.substring(0, 200) + "...",
82
- signatureLength: signatureHex.length,
83
- signaturePreview: signatureHex.substring(0, 16) + "..."
84
- }, null, 2));
85
- return signatureHex;
86
- }
87
- if (typeof crypto !== "undefined" && crypto.subtle) {
88
- const encoder = new TextEncoder();
89
- const keyData = encoder.encode(secret);
90
- const messageData = encoder.encode(message);
91
- const key = await crypto.subtle.importKey(
92
- "raw",
93
- keyData,
94
- { name: "HMAC", hash: "SHA-256" },
95
- false,
96
- ["sign"]
97
- );
98
- const signature = await crypto.subtle.sign("HMAC", key, messageData);
99
- const hashArray = Array.from(new Uint8Array(signature));
100
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
101
- }
102
- throw new Error("HMAC-SHA256 not available in this environment");
54
+ const hmac = crypto.createHmac("sha256", secret);
55
+ hmac.update(message, "utf8");
56
+ const signatureHex = hmac.digest("hex");
57
+ console.error("[HMAC CRYPTO DEBUG] Signature computation:", JSON.stringify({
58
+ secretLength: secret.length,
59
+ messageLength: message.length,
60
+ messagePreview: message.substring(0, 200) + "...",
61
+ signatureLength: signatureHex.length,
62
+ signaturePreview: signatureHex.substring(0, 16) + "..."
63
+ }, null, 2));
64
+ return signatureHex;
103
65
  }
104
66
 
105
67
  // src/auth/HmacSigner.ts
@@ -132,26 +94,7 @@ var HmacSigner = class {
132
94
  // Used as nonce in canonical string
133
95
  bodyHash
134
96
  ].join("\n");
135
- console.error("[HMAC SIGNER DEBUG] Canonical request string:", JSON.stringify({
136
- method: method.toUpperCase(),
137
- path,
138
- tenantId,
139
- keyId: this.keyId,
140
- timestampMs: String(timestampMs),
141
- requestId,
142
- bodyHash,
143
- signingStringLength: signingString.length,
144
- signingStringPreview: signingString.substring(0, 200) + "...",
145
- bodyJsonLength: bodyJson.length,
146
- bodyJsonPreview: bodyJson.substring(0, 200) + "..."
147
- }, null, 2));
148
97
  const signature = await hmacSha256(this.secret, signingString);
149
- console.error("[HMAC SIGNER DEBUG] Signature computed:", JSON.stringify({
150
- signatureLength: signature.length,
151
- signaturePreview: signature.substring(0, 16) + "...",
152
- secretLength: this.secret.length,
153
- secretPreview: this.secret.substring(0, 4) + "..." + this.secret.substring(this.secret.length - 4)
154
- }, null, 2));
155
98
  return {
156
99
  "X-GATE-TENANT-ID": tenantId,
157
100
  "X-GATE-KEY-ID": this.keyId,
@@ -350,6 +293,10 @@ async function retryWithBackoff(fn, options = {}) {
350
293
  if (!isRetryable) {
351
294
  throw error;
352
295
  }
296
+ const status = error instanceof Response && error.status || error && typeof error === "object" && "status" in error && error.status || error && typeof error === "object" && "statusCode" in error && error.statusCode;
297
+ const errName = error instanceof Error ? error.name : error && typeof error === "object" && "code" in error ? error.code : "Unknown";
298
+ const extra = ` attempt=${attempt}/${opts.maxAttempts} status=${status ?? "n/a"} err=${errName}`;
299
+ console.warn("[GATE SDK] X-BlockIntel-Degraded: true (reason=retry)" + extra);
353
300
  const delay = calculateBackoffDelay(attempt, opts);
354
301
  await new Promise((resolve) => setTimeout(resolve, delay));
355
302
  }
@@ -357,17 +304,73 @@ async function retryWithBackoff(fn, options = {}) {
357
304
  throw lastError;
358
305
  }
359
306
 
307
+ // src/utils/sanitize.ts
308
+ var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
309
+ "authorization",
310
+ "x-api-key",
311
+ "x-gate-heartbeat-key",
312
+ "x-gate-signature",
313
+ "cookie"
314
+ ]);
315
+ var MAX_STRING_LENGTH = 80;
316
+ function sanitizeHeaders(headers) {
317
+ const out = {};
318
+ for (const [key, value] of Object.entries(headers)) {
319
+ const lower = key.toLowerCase();
320
+ if (SENSITIVE_HEADER_NAMES.has(lower) || lower.includes("signature") || lower.includes("secret") || lower.includes("token")) {
321
+ out[key] = value ? "[REDACTED]" : "[empty]";
322
+ } else {
323
+ out[key] = truncate(String(value), MAX_STRING_LENGTH);
324
+ }
325
+ }
326
+ return out;
327
+ }
328
+ function sanitizeBodyShape(body) {
329
+ if (body === null || body === void 0) {
330
+ return {};
331
+ }
332
+ if (typeof body !== "object") {
333
+ return { _: typeof body };
334
+ }
335
+ if (Array.isArray(body)) {
336
+ return { _: "array", length: String(body.length) };
337
+ }
338
+ const out = {};
339
+ for (const key of Object.keys(body).sort()) {
340
+ const val = body[key];
341
+ if (val !== null && typeof val === "object" && !Array.isArray(val)) {
342
+ out[key] = "object";
343
+ } else if (Array.isArray(val)) {
344
+ out[key] = "array";
345
+ } else {
346
+ out[key] = typeof val;
347
+ }
348
+ }
349
+ return out;
350
+ }
351
+ function truncate(s, max) {
352
+ if (s.length <= max) return s;
353
+ return s.slice(0, max) + "...";
354
+ }
355
+ function isDebugEnabled(debugOption) {
356
+ if (debugOption === true) return true;
357
+ if (typeof process !== "undefined" && process.env.GATE_SDK_DEBUG === "1") return true;
358
+ return false;
359
+ }
360
+
360
361
  // src/http/HttpClient.ts
361
362
  var HttpClient = class {
362
363
  baseUrl;
363
364
  timeoutMs;
364
365
  userAgent;
365
366
  retryOptions;
367
+ debug;
366
368
  constructor(config) {
367
369
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
368
370
  this.timeoutMs = config.timeoutMs ?? 15e3;
369
371
  this.userAgent = config.userAgent ?? "blockintel-gate-sdk/0.1.0";
370
372
  this.retryOptions = config.retryOptions;
373
+ this.debug = isDebugEnabled(config.debug);
371
374
  if (!this.baseUrl) {
372
375
  throw new Error("baseUrl is required");
373
376
  }
@@ -386,7 +389,6 @@ var HttpClient = class {
386
389
  const controller = new AbortController();
387
390
  const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
388
391
  let requestDetailsForLogging = null;
389
- let requestDetailsSet = false;
390
392
  try {
391
393
  const response = await retryWithBackoff(
392
394
  async () => {
@@ -409,31 +411,22 @@ var HttpClient = class {
409
411
  fetchOptions.body = JSON.stringify(body);
410
412
  }
411
413
  }
412
- const logHeaders = {};
413
- if (fetchOptions.headers) {
414
- Object.entries(fetchOptions.headers).forEach(([key, value]) => {
415
- if (key.toLowerCase().includes("signature") || key.toLowerCase().includes("secret")) {
416
- logHeaders[key] = String(value).substring(0, 8) + "...";
417
- } else {
418
- logHeaders[key] = String(value);
419
- }
420
- });
421
- }
422
414
  const bodyStr = typeof fetchOptions.body === "string" ? fetchOptions.body : null;
423
- const details = {
424
- headers: logHeaders,
425
- bodyLength: bodyStr ? bodyStr.length : 0,
426
- bodyPreview: bodyStr ? bodyStr.substring(0, 300) : null
415
+ requestDetailsForLogging = {
416
+ headers: this.debug ? sanitizeHeaders(requestHeaders) : {},
417
+ bodyLength: bodyStr ? bodyStr.length : 0
427
418
  };
428
- requestDetailsForLogging = details;
429
- requestDetailsSet = true;
430
- console.error("[HTTP CLIENT DEBUG] Sending request:", JSON.stringify({
431
- url,
432
- method,
433
- headers: logHeaders,
434
- bodyLength: requestDetailsForLogging.bodyLength,
435
- bodyPreview: requestDetailsForLogging.bodyPreview
436
- }, null, 2));
419
+ if (this.debug) {
420
+ const bodyShape = body && typeof body === "object" ? sanitizeBodyShape(body) : {};
421
+ console.error("[GATE SDK] Request:", JSON.stringify({
422
+ url,
423
+ method,
424
+ headerNames: Object.keys(requestHeaders),
425
+ headersRedacted: requestDetailsForLogging.headers,
426
+ bodyLength: requestDetailsForLogging.bodyLength,
427
+ bodyKeysAndTypes: bodyShape
428
+ }, null, 2));
429
+ }
437
430
  const res = await fetch(url, fetchOptions);
438
431
  if (!res.ok && isRetryableStatus(res.status)) {
439
432
  throw res;
@@ -451,26 +444,24 @@ var HttpClient = class {
451
444
  clearTimeout(timeoutId);
452
445
  let data;
453
446
  const contentType = response.headers.get("content-type");
454
- console.error("[HTTP CLIENT DEBUG] Response received:", JSON.stringify({
455
- status: response.status,
456
- ok: response.ok,
457
- statusText: response.statusText,
458
- contentType,
459
- url: response.url
460
- }, null, 2));
447
+ if (this.debug) {
448
+ console.error("[GATE SDK] Response:", JSON.stringify({
449
+ status: response.status,
450
+ ok: response.ok,
451
+ url: response.url
452
+ }, null, 2));
453
+ }
461
454
  if (contentType && contentType.includes("application/json")) {
462
455
  try {
463
456
  const jsonText = await response.text();
464
- console.error("[HTTP CLIENT DEBUG] Response body (first 500 chars):", jsonText.substring(0, 500));
465
457
  data = JSON.parse(jsonText);
466
- console.error("[HTTP CLIENT DEBUG] Parsed JSON:", JSON.stringify({
467
- hasSuccess: typeof data?.success !== "undefined",
468
- success: data?.success,
469
- hasData: typeof data?.data !== "undefined",
470
- hasError: typeof data?.error !== "undefined"
471
- }, null, 2));
458
+ if (this.debug && data && typeof data === "object") {
459
+ console.error("[GATE SDK] Response keys:", Object.keys(data));
460
+ }
472
461
  } catch (parseError) {
473
- console.error("[HTTP CLIENT DEBUG] JSON parse error:", parseError);
462
+ if (this.debug) {
463
+ console.error("[GATE SDK] JSON parse error:", parseError instanceof Error ? parseError.message : String(parseError));
464
+ }
474
465
  throw new GateError(
475
466
  "INVALID_RESPONSE" /* INVALID_RESPONSE */,
476
467
  "Failed to parse JSON response",
@@ -498,26 +489,12 @@ var HttpClient = class {
498
489
  response.headers.forEach((value, key) => {
499
490
  responseHeaders[key] = value;
500
491
  });
501
- if (response.status === 401) {
502
- console.error("[HTTP CLIENT DEBUG] 401 UNAUTHORIZED - Full request details:", JSON.stringify({
492
+ if (this.debug) {
493
+ console.error("[GATE SDK] Error response:", JSON.stringify({
503
494
  status: response.status,
504
- statusText: response.statusText,
505
495
  url: response.url,
506
- requestMethod: method,
507
496
  requestPath: path,
508
- requestHeaders: requestDetailsForLogging ? requestDetailsForLogging.headers : {},
509
- responseHeaders,
510
- responseData: data,
511
- bodyLength: requestDetailsForLogging ? requestDetailsForLogging.bodyLength : 0,
512
- bodyPreview: requestDetailsForLogging ? requestDetailsForLogging.bodyPreview : null
513
- }, null, 2));
514
- } else {
515
- console.error("[HTTP CLIENT DEBUG] Response not OK:", JSON.stringify({
516
- status: response.status,
517
- statusText: response.statusText,
518
- url: response.url,
519
- headers: responseHeaders,
520
- data
497
+ responseKeys: data && typeof data === "object" ? Object.keys(data) : []
521
498
  }, null, 2));
522
499
  }
523
500
  const errorCode = this.statusToErrorCode(response.status);
@@ -529,7 +506,6 @@ var HttpClient = class {
529
506
  details: data
530
507
  });
531
508
  }
532
- console.error("[HTTP CLIENT DEBUG] Response OK, returning data");
533
509
  return data;
534
510
  } catch (error) {
535
511
  clearTimeout(timeoutId);
@@ -982,7 +958,7 @@ function defaultExtractTxIntent(command) {
982
958
  throw new Error("SignCommand missing required Message property");
983
959
  }
984
960
  const messageBuffer = message instanceof Buffer ? message : Buffer.from(message);
985
- const messageHash = crypto$1.createHash("sha256").update(messageBuffer).digest("hex");
961
+ const messageHash = crypto.createHash("sha256").update(messageBuffer).digest("hex");
986
962
  return {
987
963
  networkFamily: "OTHER",
988
964
  toAddress: void 0,
@@ -1088,6 +1064,8 @@ var HeartbeatManager = class {
1088
1064
  // Unique per process
1089
1065
  sdkVersion;
1090
1066
  // SDK version for tracking
1067
+ apiKey;
1068
+ // x-gate-heartbeat-key for Control Plane auth
1091
1069
  currentToken = null;
1092
1070
  refreshTimer = null;
1093
1071
  started = false;
@@ -1100,19 +1078,22 @@ var HeartbeatManager = class {
1100
1078
  this.signerId = options.signerId;
1101
1079
  this.environment = options.environment ?? "prod";
1102
1080
  this.baseRefreshIntervalSeconds = options.refreshIntervalSeconds ?? 10;
1081
+ this.apiKey = options.apiKey;
1103
1082
  this.clientInstanceId = options.clientInstanceId || uuid.v4();
1104
1083
  this.sdkVersion = options.sdkVersion || "1.0.0";
1084
+ this.apiKey = options.apiKey;
1105
1085
  }
1106
1086
  /**
1107
- * Start background heartbeat refresher
1087
+ * Start background heartbeat refresher.
1088
+ * Optionally wait for initial token (first evaluate() will otherwise wait up to 2s for token).
1108
1089
  */
1109
- start() {
1090
+ start(options) {
1110
1091
  if (this.started) {
1111
1092
  return;
1112
1093
  }
1113
1094
  this.started = true;
1114
1095
  this.acquireHeartbeat().catch((error) => {
1115
- console.error("[HEARTBEAT] Failed to acquire initial heartbeat:", error);
1096
+ console.error("[HEARTBEAT] Failed to acquire initial heartbeat:", error instanceof Error ? error.message : error);
1116
1097
  });
1117
1098
  this.scheduleNextRefresh();
1118
1099
  }
@@ -1195,12 +1176,23 @@ var HeartbeatManager = class {
1195
1176
  /**
1196
1177
  * Acquire a new heartbeat token from Control Plane
1197
1178
  * NEVER logs token value (security)
1179
+ * Requires x-gate-heartbeat-key header (apiKey) for authentication.
1198
1180
  */
1199
1181
  async acquireHeartbeat() {
1182
+ if (!this.apiKey || this.apiKey.length === 0) {
1183
+ throw new GateError(
1184
+ "UNAUTHORIZED" /* UNAUTHORIZED */,
1185
+ "Heartbeat API key is required. Set GATE_HEARTBEAT_KEY in environment or pass heartbeatApiKey in GateClientConfig.",
1186
+ {}
1187
+ );
1188
+ }
1200
1189
  try {
1201
1190
  const response = await this.httpClient.request({
1202
1191
  method: "POST",
1203
1192
  path: "/api/v1/gate/heartbeat",
1193
+ headers: {
1194
+ "x-gate-heartbeat-key": this.apiKey
1195
+ },
1204
1196
  body: {
1205
1197
  tenantId: this.tenantId,
1206
1198
  signerId: this.signerId,
@@ -1528,7 +1520,8 @@ var GateClient = class {
1528
1520
  this.httpClient = new HttpClient({
1529
1521
  baseUrl: config.baseUrl,
1530
1522
  timeoutMs: config.timeoutMs,
1531
- userAgent: config.userAgent
1523
+ userAgent: config.userAgent,
1524
+ debug: config.debug
1532
1525
  });
1533
1526
  if (config.enableStepUp) {
1534
1527
  this.stepUpPoller = new StepUpPoller({
@@ -1549,6 +1542,12 @@ var GateClient = class {
1549
1542
  console.warn("[GATE CLIENT] LOCAL MODE ENABLED - Auth, heartbeat, and break-glass are disabled");
1550
1543
  this.heartbeatManager = null;
1551
1544
  } else {
1545
+ const heartbeatApiKey = config.heartbeatApiKey ?? (typeof process !== "undefined" ? process.env.GATE_HEARTBEAT_KEY : void 0);
1546
+ if (!heartbeatApiKey || heartbeatApiKey.length === 0) {
1547
+ throw new Error(
1548
+ "GATE_HEARTBEAT_KEY environment variable or heartbeatApiKey in config is required for heartbeat authentication. Set GATE_HEARTBEAT_KEY in your environment or pass heartbeatApiKey in GateClientConfig."
1549
+ );
1550
+ }
1552
1551
  let controlPlaneUrl = config.baseUrl;
1553
1552
  if (controlPlaneUrl.includes("/defense")) {
1554
1553
  controlPlaneUrl = controlPlaneUrl.split("/defense")[0];
@@ -1568,7 +1567,8 @@ var GateClient = class {
1568
1567
  tenantId: config.tenantId,
1569
1568
  signerId: initialSignerId,
1570
1569
  environment: config.environment ?? "prod",
1571
- refreshIntervalSeconds: config.heartbeatRefreshIntervalSeconds ?? 10
1570
+ refreshIntervalSeconds: config.heartbeatRefreshIntervalSeconds ?? 10,
1571
+ apiKey: heartbeatApiKey
1572
1572
  });
1573
1573
  this.heartbeatManager.start();
1574
1574
  }
@@ -1657,7 +1657,9 @@ var GateClient = class {
1657
1657
  delete txIntent.from;
1658
1658
  }
1659
1659
  const signingContext = {
1660
- ...req.signingContext
1660
+ ...req.signingContext,
1661
+ actorPrincipal: req.signingContext?.actorPrincipal ?? req.signingContext?.signerId ?? "gate-sdk-client",
1662
+ signerId: req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? "gate-sdk-client"
1661
1663
  };
1662
1664
  if (heartbeatToken) {
1663
1665
  signingContext.heartbeatToken = heartbeatToken;
@@ -1673,17 +1675,16 @@ var GateClient = class {
1673
1675
  };
1674
1676
  }
1675
1677
  let body = {
1678
+ tenantId: this.config.tenantId,
1676
1679
  requestId,
1677
1680
  timestampMs,
1678
1681
  txIntent,
1679
1682
  signingContext,
1680
1683
  // Add SDK info (required by Hot Path validation)
1681
- // Note: Must match Python SDK name for consistent canonical JSON
1682
1684
  sdk: {
1683
1685
  name: "gate-sdk",
1684
1686
  version: "0.1.0"
1685
1687
  },
1686
- // Add mode and connection failure strategy
1687
1688
  mode: requestMode,
1688
1689
  onConnectionFailure: this.onConnectionFailure
1689
1690
  };
@@ -1713,15 +1714,6 @@ var GateClient = class {
1713
1714
  });
1714
1715
  headers = { ...hmacHeaders };
1715
1716
  body.__canonicalJson = canonicalBodyJson;
1716
- const debugHeaders = {};
1717
- Object.entries(headers).forEach(([key, value]) => {
1718
- if (key.toLowerCase().includes("signature")) {
1719
- debugHeaders[key] = value.substring(0, 8) + "...";
1720
- } else {
1721
- debugHeaders[key] = value;
1722
- }
1723
- });
1724
- console.error("[GATE CLIENT DEBUG] HMAC headers prepared:", JSON.stringify(debugHeaders, null, 2));
1725
1717
  } else if (this.apiKeyAuth) {
1726
1718
  const apiKeyHeaders = this.apiKeyAuth.createHeaders({
1727
1719
  tenantId: this.config.tenantId,
@@ -1729,7 +1721,6 @@ var GateClient = class {
1729
1721
  requestId
1730
1722
  });
1731
1723
  headers = { ...apiKeyHeaders };
1732
- console.error("[GATE CLIENT DEBUG] API key headers prepared:", JSON.stringify(headers, null, 2));
1733
1724
  } else {
1734
1725
  throw new Error("No authentication configured");
1735
1726
  }
@@ -1863,6 +1854,7 @@ var GateClient = class {
1863
1854
  tenantId: this.config.tenantId,
1864
1855
  mode: requestMode
1865
1856
  });
1857
+ console.warn("[GATE SDK] X-BlockIntel-Degraded: true (reason: fail_open)");
1866
1858
  this.metrics.recordRequest("FAIL_OPEN", Date.now() - startTime);
1867
1859
  return {
1868
1860
  decision: "ALLOW",
@@ -1894,6 +1886,10 @@ var GateClient = class {
1894
1886
  }
1895
1887
  throw error;
1896
1888
  }
1889
+ if (error instanceof GateError && error.code === "RATE_LIMITED" /* RATE_LIMITED */) {
1890
+ console.warn("[GATE SDK] X-BlockIntel-Degraded: true (reason: 429)");
1891
+ throw error;
1892
+ }
1897
1893
  if (error instanceof BlockIntelBlockedError || error instanceof BlockIntelStepUpRequiredError) {
1898
1894
  throw error;
1899
1895
  }
@@ -1906,6 +1902,7 @@ var GateClient = class {
1906
1902
  */
1907
1903
  handleFailSafe(mode, error, requestId) {
1908
1904
  if (mode === "ALLOW_ON_TIMEOUT") {
1905
+ console.warn("[GATE SDK] X-BlockIntel-Degraded: true (reason: fail_safe_allow)");
1909
1906
  return {
1910
1907
  decision: "ALLOW",
1911
1908
  reasonCodes: ["FAIL_SAFE_ALLOW"],
@@ -1916,6 +1913,7 @@ var GateClient = class {
1916
1913
  return null;
1917
1914
  }
1918
1915
  if (mode === "BLOCK_ON_ANOMALY") {
1916
+ console.warn("[GATE SDK] X-BlockIntel-Degraded: true (reason: fail_safe_allow)");
1919
1917
  return {
1920
1918
  decision: "ALLOW",
1921
1919
  reasonCodes: ["FAIL_SAFE_ALLOW"],