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/dist/index.d.cts CHANGED
@@ -252,6 +252,10 @@ interface GateClientConfig {
252
252
  onMetrics?: (metrics: Metrics) => void | Promise<void>;
253
253
  signerId?: string;
254
254
  heartbeatRefreshIntervalSeconds?: number;
255
+ /** API key for Control Plane heartbeat endpoint (x-gate-heartbeat-key). Required when not local. Fallback: GATE_HEARTBEAT_KEY env. */
256
+ heartbeatApiKey?: string;
257
+ /** When true or GATE_SDK_DEBUG=1, log sanitized request/response (no secrets, no body values). */
258
+ debug?: boolean;
255
259
  /**
256
260
  * Break-glass token (optional, for emergency override)
257
261
  *
@@ -671,6 +675,8 @@ interface HttpClientConfig {
671
675
  baseUrl: string;
672
676
  timeoutMs?: number;
673
677
  userAgent?: string;
678
+ /** When true or GATE_SDK_DEBUG=1, log sanitized request/response (no secrets, no body values). */
679
+ debug?: boolean;
674
680
  retryOptions?: {
675
681
  maxAttempts?: number;
676
682
  baseDelayMs?: number;
@@ -693,6 +699,7 @@ declare class HttpClient {
693
699
  private readonly timeoutMs;
694
700
  private readonly userAgent;
695
701
  private readonly retryOptions;
702
+ private readonly debug;
696
703
  constructor(config: HttpClientConfig);
697
704
  /**
698
705
  * Make an HTTP request with retry and timeout
@@ -731,6 +738,7 @@ declare class HeartbeatManager {
731
738
  private readonly baseRefreshIntervalSeconds;
732
739
  private readonly clientInstanceId;
733
740
  private readonly sdkVersion;
741
+ private readonly apiKey;
734
742
  private currentToken;
735
743
  private refreshTimer;
736
744
  private started;
@@ -744,11 +752,16 @@ declare class HeartbeatManager {
744
752
  refreshIntervalSeconds?: number;
745
753
  clientInstanceId?: string;
746
754
  sdkVersion?: string;
755
+ /** API key for heartbeat endpoint auth (x-gate-heartbeat-key). Required unless local mode. */
756
+ apiKey?: string;
747
757
  });
748
758
  /**
749
- * Start background heartbeat refresher
759
+ * Start background heartbeat refresher.
760
+ * Optionally wait for initial token (first evaluate() will otherwise wait up to 2s for token).
750
761
  */
751
- start(): void;
762
+ start(options?: {
763
+ waitForInitial?: boolean;
764
+ }): void;
752
765
  /**
753
766
  * Schedule next refresh with jitter and backoff
754
767
  */
@@ -776,6 +789,7 @@ declare class HeartbeatManager {
776
789
  /**
777
790
  * Acquire a new heartbeat token from Control Plane
778
791
  * NEVER logs token value (security)
792
+ * Requires x-gate-heartbeat-key header (apiKey) for authentication.
779
793
  */
780
794
  private acquireHeartbeat;
781
795
  /**
package/dist/index.d.ts CHANGED
@@ -252,6 +252,10 @@ interface GateClientConfig {
252
252
  onMetrics?: (metrics: Metrics) => void | Promise<void>;
253
253
  signerId?: string;
254
254
  heartbeatRefreshIntervalSeconds?: number;
255
+ /** API key for Control Plane heartbeat endpoint (x-gate-heartbeat-key). Required when not local. Fallback: GATE_HEARTBEAT_KEY env. */
256
+ heartbeatApiKey?: string;
257
+ /** When true or GATE_SDK_DEBUG=1, log sanitized request/response (no secrets, no body values). */
258
+ debug?: boolean;
255
259
  /**
256
260
  * Break-glass token (optional, for emergency override)
257
261
  *
@@ -671,6 +675,8 @@ interface HttpClientConfig {
671
675
  baseUrl: string;
672
676
  timeoutMs?: number;
673
677
  userAgent?: string;
678
+ /** When true or GATE_SDK_DEBUG=1, log sanitized request/response (no secrets, no body values). */
679
+ debug?: boolean;
674
680
  retryOptions?: {
675
681
  maxAttempts?: number;
676
682
  baseDelayMs?: number;
@@ -693,6 +699,7 @@ declare class HttpClient {
693
699
  private readonly timeoutMs;
694
700
  private readonly userAgent;
695
701
  private readonly retryOptions;
702
+ private readonly debug;
696
703
  constructor(config: HttpClientConfig);
697
704
  /**
698
705
  * Make an HTTP request with retry and timeout
@@ -731,6 +738,7 @@ declare class HeartbeatManager {
731
738
  private readonly baseRefreshIntervalSeconds;
732
739
  private readonly clientInstanceId;
733
740
  private readonly sdkVersion;
741
+ private readonly apiKey;
734
742
  private currentToken;
735
743
  private refreshTimer;
736
744
  private started;
@@ -744,11 +752,16 @@ declare class HeartbeatManager {
744
752
  refreshIntervalSeconds?: number;
745
753
  clientInstanceId?: string;
746
754
  sdkVersion?: string;
755
+ /** API key for heartbeat endpoint auth (x-gate-heartbeat-key). Required unless local mode. */
756
+ apiKey?: string;
747
757
  });
748
758
  /**
749
- * Start background heartbeat refresher
759
+ * Start background heartbeat refresher.
760
+ * Optionally wait for initial token (first evaluate() will otherwise wait up to 2s for token).
750
761
  */
751
- start(): void;
762
+ start(options?: {
763
+ waitForInitial?: boolean;
764
+ }): void;
752
765
  /**
753
766
  * Schedule next refresh with jitter and backoff
754
767
  */
@@ -776,6 +789,7 @@ declare class HeartbeatManager {
776
789
  /**
777
790
  * Acquire a new heartbeat token from Control Plane
778
791
  * NEVER logs token value (security)
792
+ * Requires x-gate-heartbeat-key header (apiKey) for authentication.
779
793
  */
780
794
  private acquireHeartbeat;
781
795
  /**
package/dist/index.js CHANGED
@@ -1,15 +1,9 @@
1
+ import { createHash, createHmac } from 'crypto';
1
2
  import { v4 } from 'uuid';
2
3
  import { SignCommand } from '@aws-sdk/client-kms';
3
- import { createHash } from 'crypto';
4
4
 
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
- }) : x)(function(x) {
10
- if (typeof require !== "undefined") return require.apply(this, arguments);
11
- throw Error('Dynamic require of "' + x + '" is not supported');
12
- });
13
7
  var __esm = (fn, res) => function __init() {
14
8
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
9
  };
@@ -46,56 +40,24 @@ function canonicalizeJson(obj) {
46
40
  return JSON.stringify(sorted);
47
41
  }
48
42
  async function sha256Hex(input) {
49
- if (typeof crypto !== "undefined" && crypto.subtle) {
50
- const encoder = new TextEncoder();
51
- const data = encoder.encode(input);
52
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
53
- const hashArray = Array.from(new Uint8Array(hashBuffer));
54
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
55
- }
56
- if (typeof __require !== "undefined") {
57
- const crypto2 = __require("crypto");
58
- return crypto2.createHash("sha256").update(input, "utf8").digest("hex");
59
- }
60
- throw new Error("SHA-256 not available in this environment");
43
+ return createHash("sha256").update(input, "utf8").digest("hex");
61
44
  }
62
45
  var init_canonicalJson = __esm({
63
46
  "src/utils/canonicalJson.ts"() {
64
47
  }
65
48
  });
66
-
67
- // src/utils/crypto.ts
68
49
  async function hmacSha256(secret, message) {
69
- if (typeof __require !== "undefined") {
70
- const crypto2 = __require("crypto");
71
- const hmac = crypto2.createHmac("sha256", secret);
72
- hmac.update(message, "utf8");
73
- const signatureHex = hmac.digest("hex");
74
- console.error("[HMAC CRYPTO DEBUG] Signature computation:", JSON.stringify({
75
- secretLength: secret.length,
76
- messageLength: message.length,
77
- messagePreview: message.substring(0, 200) + "...",
78
- signatureLength: signatureHex.length,
79
- signaturePreview: signatureHex.substring(0, 16) + "..."
80
- }, null, 2));
81
- return signatureHex;
82
- }
83
- if (typeof crypto !== "undefined" && crypto.subtle) {
84
- const encoder = new TextEncoder();
85
- const keyData = encoder.encode(secret);
86
- const messageData = encoder.encode(message);
87
- const key = await crypto.subtle.importKey(
88
- "raw",
89
- keyData,
90
- { name: "HMAC", hash: "SHA-256" },
91
- false,
92
- ["sign"]
93
- );
94
- const signature = await crypto.subtle.sign("HMAC", key, messageData);
95
- const hashArray = Array.from(new Uint8Array(signature));
96
- return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
97
- }
98
- throw new Error("HMAC-SHA256 not available in this environment");
50
+ const hmac = createHmac("sha256", secret);
51
+ hmac.update(message, "utf8");
52
+ const signatureHex = hmac.digest("hex");
53
+ console.error("[HMAC CRYPTO DEBUG] Signature computation:", JSON.stringify({
54
+ secretLength: secret.length,
55
+ messageLength: message.length,
56
+ messagePreview: message.substring(0, 200) + "...",
57
+ signatureLength: signatureHex.length,
58
+ signaturePreview: signatureHex.substring(0, 16) + "..."
59
+ }, null, 2));
60
+ return signatureHex;
99
61
  }
100
62
 
101
63
  // src/auth/HmacSigner.ts
@@ -128,26 +90,7 @@ var HmacSigner = class {
128
90
  // Used as nonce in canonical string
129
91
  bodyHash
130
92
  ].join("\n");
131
- console.error("[HMAC SIGNER DEBUG] Canonical request string:", JSON.stringify({
132
- method: method.toUpperCase(),
133
- path,
134
- tenantId,
135
- keyId: this.keyId,
136
- timestampMs: String(timestampMs),
137
- requestId,
138
- bodyHash,
139
- signingStringLength: signingString.length,
140
- signingStringPreview: signingString.substring(0, 200) + "...",
141
- bodyJsonLength: bodyJson.length,
142
- bodyJsonPreview: bodyJson.substring(0, 200) + "..."
143
- }, null, 2));
144
93
  const signature = await hmacSha256(this.secret, signingString);
145
- console.error("[HMAC SIGNER DEBUG] Signature computed:", JSON.stringify({
146
- signatureLength: signature.length,
147
- signaturePreview: signature.substring(0, 16) + "...",
148
- secretLength: this.secret.length,
149
- secretPreview: this.secret.substring(0, 4) + "..." + this.secret.substring(this.secret.length - 4)
150
- }, null, 2));
151
94
  return {
152
95
  "X-GATE-TENANT-ID": tenantId,
153
96
  "X-GATE-KEY-ID": this.keyId,
@@ -346,6 +289,10 @@ async function retryWithBackoff(fn, options = {}) {
346
289
  if (!isRetryable) {
347
290
  throw error;
348
291
  }
292
+ 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;
293
+ const errName = error instanceof Error ? error.name : error && typeof error === "object" && "code" in error ? error.code : "Unknown";
294
+ const extra = ` attempt=${attempt}/${opts.maxAttempts} status=${status ?? "n/a"} err=${errName}`;
295
+ console.warn("[GATE SDK] X-BlockIntel-Degraded: true (reason=retry)" + extra);
349
296
  const delay = calculateBackoffDelay(attempt, opts);
350
297
  await new Promise((resolve) => setTimeout(resolve, delay));
351
298
  }
@@ -353,17 +300,73 @@ async function retryWithBackoff(fn, options = {}) {
353
300
  throw lastError;
354
301
  }
355
302
 
303
+ // src/utils/sanitize.ts
304
+ var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
305
+ "authorization",
306
+ "x-api-key",
307
+ "x-gate-heartbeat-key",
308
+ "x-gate-signature",
309
+ "cookie"
310
+ ]);
311
+ var MAX_STRING_LENGTH = 80;
312
+ function sanitizeHeaders(headers) {
313
+ const out = {};
314
+ for (const [key, value] of Object.entries(headers)) {
315
+ const lower = key.toLowerCase();
316
+ if (SENSITIVE_HEADER_NAMES.has(lower) || lower.includes("signature") || lower.includes("secret") || lower.includes("token")) {
317
+ out[key] = value ? "[REDACTED]" : "[empty]";
318
+ } else {
319
+ out[key] = truncate(String(value), MAX_STRING_LENGTH);
320
+ }
321
+ }
322
+ return out;
323
+ }
324
+ function sanitizeBodyShape(body) {
325
+ if (body === null || body === void 0) {
326
+ return {};
327
+ }
328
+ if (typeof body !== "object") {
329
+ return { _: typeof body };
330
+ }
331
+ if (Array.isArray(body)) {
332
+ return { _: "array", length: String(body.length) };
333
+ }
334
+ const out = {};
335
+ for (const key of Object.keys(body).sort()) {
336
+ const val = body[key];
337
+ if (val !== null && typeof val === "object" && !Array.isArray(val)) {
338
+ out[key] = "object";
339
+ } else if (Array.isArray(val)) {
340
+ out[key] = "array";
341
+ } else {
342
+ out[key] = typeof val;
343
+ }
344
+ }
345
+ return out;
346
+ }
347
+ function truncate(s, max) {
348
+ if (s.length <= max) return s;
349
+ return s.slice(0, max) + "...";
350
+ }
351
+ function isDebugEnabled(debugOption) {
352
+ if (debugOption === true) return true;
353
+ if (typeof process !== "undefined" && process.env.GATE_SDK_DEBUG === "1") return true;
354
+ return false;
355
+ }
356
+
356
357
  // src/http/HttpClient.ts
357
358
  var HttpClient = class {
358
359
  baseUrl;
359
360
  timeoutMs;
360
361
  userAgent;
361
362
  retryOptions;
363
+ debug;
362
364
  constructor(config) {
363
365
  this.baseUrl = config.baseUrl.replace(/\/$/, "");
364
366
  this.timeoutMs = config.timeoutMs ?? 15e3;
365
367
  this.userAgent = config.userAgent ?? "blockintel-gate-sdk/0.1.0";
366
368
  this.retryOptions = config.retryOptions;
369
+ this.debug = isDebugEnabled(config.debug);
367
370
  if (!this.baseUrl) {
368
371
  throw new Error("baseUrl is required");
369
372
  }
@@ -382,7 +385,6 @@ var HttpClient = class {
382
385
  const controller = new AbortController();
383
386
  const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
384
387
  let requestDetailsForLogging = null;
385
- let requestDetailsSet = false;
386
388
  try {
387
389
  const response = await retryWithBackoff(
388
390
  async () => {
@@ -405,31 +407,22 @@ var HttpClient = class {
405
407
  fetchOptions.body = JSON.stringify(body);
406
408
  }
407
409
  }
408
- const logHeaders = {};
409
- if (fetchOptions.headers) {
410
- Object.entries(fetchOptions.headers).forEach(([key, value]) => {
411
- if (key.toLowerCase().includes("signature") || key.toLowerCase().includes("secret")) {
412
- logHeaders[key] = String(value).substring(0, 8) + "...";
413
- } else {
414
- logHeaders[key] = String(value);
415
- }
416
- });
417
- }
418
410
  const bodyStr = typeof fetchOptions.body === "string" ? fetchOptions.body : null;
419
- const details = {
420
- headers: logHeaders,
421
- bodyLength: bodyStr ? bodyStr.length : 0,
422
- bodyPreview: bodyStr ? bodyStr.substring(0, 300) : null
411
+ requestDetailsForLogging = {
412
+ headers: this.debug ? sanitizeHeaders(requestHeaders) : {},
413
+ bodyLength: bodyStr ? bodyStr.length : 0
423
414
  };
424
- requestDetailsForLogging = details;
425
- requestDetailsSet = true;
426
- console.error("[HTTP CLIENT DEBUG] Sending request:", JSON.stringify({
427
- url,
428
- method,
429
- headers: logHeaders,
430
- bodyLength: requestDetailsForLogging.bodyLength,
431
- bodyPreview: requestDetailsForLogging.bodyPreview
432
- }, null, 2));
415
+ if (this.debug) {
416
+ const bodyShape = body && typeof body === "object" ? sanitizeBodyShape(body) : {};
417
+ console.error("[GATE SDK] Request:", JSON.stringify({
418
+ url,
419
+ method,
420
+ headerNames: Object.keys(requestHeaders),
421
+ headersRedacted: requestDetailsForLogging.headers,
422
+ bodyLength: requestDetailsForLogging.bodyLength,
423
+ bodyKeysAndTypes: bodyShape
424
+ }, null, 2));
425
+ }
433
426
  const res = await fetch(url, fetchOptions);
434
427
  if (!res.ok && isRetryableStatus(res.status)) {
435
428
  throw res;
@@ -447,26 +440,24 @@ var HttpClient = class {
447
440
  clearTimeout(timeoutId);
448
441
  let data;
449
442
  const contentType = response.headers.get("content-type");
450
- console.error("[HTTP CLIENT DEBUG] Response received:", JSON.stringify({
451
- status: response.status,
452
- ok: response.ok,
453
- statusText: response.statusText,
454
- contentType,
455
- url: response.url
456
- }, null, 2));
443
+ if (this.debug) {
444
+ console.error("[GATE SDK] Response:", JSON.stringify({
445
+ status: response.status,
446
+ ok: response.ok,
447
+ url: response.url
448
+ }, null, 2));
449
+ }
457
450
  if (contentType && contentType.includes("application/json")) {
458
451
  try {
459
452
  const jsonText = await response.text();
460
- console.error("[HTTP CLIENT DEBUG] Response body (first 500 chars):", jsonText.substring(0, 500));
461
453
  data = JSON.parse(jsonText);
462
- console.error("[HTTP CLIENT DEBUG] Parsed JSON:", JSON.stringify({
463
- hasSuccess: typeof data?.success !== "undefined",
464
- success: data?.success,
465
- hasData: typeof data?.data !== "undefined",
466
- hasError: typeof data?.error !== "undefined"
467
- }, null, 2));
454
+ if (this.debug && data && typeof data === "object") {
455
+ console.error("[GATE SDK] Response keys:", Object.keys(data));
456
+ }
468
457
  } catch (parseError) {
469
- console.error("[HTTP CLIENT DEBUG] JSON parse error:", parseError);
458
+ if (this.debug) {
459
+ console.error("[GATE SDK] JSON parse error:", parseError instanceof Error ? parseError.message : String(parseError));
460
+ }
470
461
  throw new GateError(
471
462
  "INVALID_RESPONSE" /* INVALID_RESPONSE */,
472
463
  "Failed to parse JSON response",
@@ -494,26 +485,12 @@ var HttpClient = class {
494
485
  response.headers.forEach((value, key) => {
495
486
  responseHeaders[key] = value;
496
487
  });
497
- if (response.status === 401) {
498
- console.error("[HTTP CLIENT DEBUG] 401 UNAUTHORIZED - Full request details:", JSON.stringify({
488
+ if (this.debug) {
489
+ console.error("[GATE SDK] Error response:", JSON.stringify({
499
490
  status: response.status,
500
- statusText: response.statusText,
501
491
  url: response.url,
502
- requestMethod: method,
503
492
  requestPath: path,
504
- requestHeaders: requestDetailsForLogging ? requestDetailsForLogging.headers : {},
505
- responseHeaders,
506
- responseData: data,
507
- bodyLength: requestDetailsForLogging ? requestDetailsForLogging.bodyLength : 0,
508
- bodyPreview: requestDetailsForLogging ? requestDetailsForLogging.bodyPreview : null
509
- }, null, 2));
510
- } else {
511
- console.error("[HTTP CLIENT DEBUG] Response not OK:", JSON.stringify({
512
- status: response.status,
513
- statusText: response.statusText,
514
- url: response.url,
515
- headers: responseHeaders,
516
- data
493
+ responseKeys: data && typeof data === "object" ? Object.keys(data) : []
517
494
  }, null, 2));
518
495
  }
519
496
  const errorCode = this.statusToErrorCode(response.status);
@@ -525,7 +502,6 @@ var HttpClient = class {
525
502
  details: data
526
503
  });
527
504
  }
528
- console.error("[HTTP CLIENT DEBUG] Response OK, returning data");
529
505
  return data;
530
506
  } catch (error) {
531
507
  clearTimeout(timeoutId);
@@ -1084,6 +1060,8 @@ var HeartbeatManager = class {
1084
1060
  // Unique per process
1085
1061
  sdkVersion;
1086
1062
  // SDK version for tracking
1063
+ apiKey;
1064
+ // x-gate-heartbeat-key for Control Plane auth
1087
1065
  currentToken = null;
1088
1066
  refreshTimer = null;
1089
1067
  started = false;
@@ -1096,19 +1074,22 @@ var HeartbeatManager = class {
1096
1074
  this.signerId = options.signerId;
1097
1075
  this.environment = options.environment ?? "prod";
1098
1076
  this.baseRefreshIntervalSeconds = options.refreshIntervalSeconds ?? 10;
1077
+ this.apiKey = options.apiKey;
1099
1078
  this.clientInstanceId = options.clientInstanceId || v4();
1100
1079
  this.sdkVersion = options.sdkVersion || "1.0.0";
1080
+ this.apiKey = options.apiKey;
1101
1081
  }
1102
1082
  /**
1103
- * Start background heartbeat refresher
1083
+ * Start background heartbeat refresher.
1084
+ * Optionally wait for initial token (first evaluate() will otherwise wait up to 2s for token).
1104
1085
  */
1105
- start() {
1086
+ start(options) {
1106
1087
  if (this.started) {
1107
1088
  return;
1108
1089
  }
1109
1090
  this.started = true;
1110
1091
  this.acquireHeartbeat().catch((error) => {
1111
- console.error("[HEARTBEAT] Failed to acquire initial heartbeat:", error);
1092
+ console.error("[HEARTBEAT] Failed to acquire initial heartbeat:", error instanceof Error ? error.message : error);
1112
1093
  });
1113
1094
  this.scheduleNextRefresh();
1114
1095
  }
@@ -1191,12 +1172,23 @@ var HeartbeatManager = class {
1191
1172
  /**
1192
1173
  * Acquire a new heartbeat token from Control Plane
1193
1174
  * NEVER logs token value (security)
1175
+ * Requires x-gate-heartbeat-key header (apiKey) for authentication.
1194
1176
  */
1195
1177
  async acquireHeartbeat() {
1178
+ if (!this.apiKey || this.apiKey.length === 0) {
1179
+ throw new GateError(
1180
+ "UNAUTHORIZED" /* UNAUTHORIZED */,
1181
+ "Heartbeat API key is required. Set GATE_HEARTBEAT_KEY in environment or pass heartbeatApiKey in GateClientConfig.",
1182
+ {}
1183
+ );
1184
+ }
1196
1185
  try {
1197
1186
  const response = await this.httpClient.request({
1198
1187
  method: "POST",
1199
1188
  path: "/api/v1/gate/heartbeat",
1189
+ headers: {
1190
+ "x-gate-heartbeat-key": this.apiKey
1191
+ },
1200
1192
  body: {
1201
1193
  tenantId: this.tenantId,
1202
1194
  signerId: this.signerId,
@@ -1524,7 +1516,8 @@ var GateClient = class {
1524
1516
  this.httpClient = new HttpClient({
1525
1517
  baseUrl: config.baseUrl,
1526
1518
  timeoutMs: config.timeoutMs,
1527
- userAgent: config.userAgent
1519
+ userAgent: config.userAgent,
1520
+ debug: config.debug
1528
1521
  });
1529
1522
  if (config.enableStepUp) {
1530
1523
  this.stepUpPoller = new StepUpPoller({
@@ -1545,6 +1538,12 @@ var GateClient = class {
1545
1538
  console.warn("[GATE CLIENT] LOCAL MODE ENABLED - Auth, heartbeat, and break-glass are disabled");
1546
1539
  this.heartbeatManager = null;
1547
1540
  } else {
1541
+ const heartbeatApiKey = config.heartbeatApiKey ?? (typeof process !== "undefined" ? process.env.GATE_HEARTBEAT_KEY : void 0);
1542
+ if (!heartbeatApiKey || heartbeatApiKey.length === 0) {
1543
+ throw new Error(
1544
+ "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."
1545
+ );
1546
+ }
1548
1547
  let controlPlaneUrl = config.baseUrl;
1549
1548
  if (controlPlaneUrl.includes("/defense")) {
1550
1549
  controlPlaneUrl = controlPlaneUrl.split("/defense")[0];
@@ -1564,7 +1563,8 @@ var GateClient = class {
1564
1563
  tenantId: config.tenantId,
1565
1564
  signerId: initialSignerId,
1566
1565
  environment: config.environment ?? "prod",
1567
- refreshIntervalSeconds: config.heartbeatRefreshIntervalSeconds ?? 10
1566
+ refreshIntervalSeconds: config.heartbeatRefreshIntervalSeconds ?? 10,
1567
+ apiKey: heartbeatApiKey
1568
1568
  });
1569
1569
  this.heartbeatManager.start();
1570
1570
  }
@@ -1653,7 +1653,9 @@ var GateClient = class {
1653
1653
  delete txIntent.from;
1654
1654
  }
1655
1655
  const signingContext = {
1656
- ...req.signingContext
1656
+ ...req.signingContext,
1657
+ actorPrincipal: req.signingContext?.actorPrincipal ?? req.signingContext?.signerId ?? "gate-sdk-client",
1658
+ signerId: req.signingContext?.signerId ?? req.signingContext?.actorPrincipal ?? "gate-sdk-client"
1657
1659
  };
1658
1660
  if (heartbeatToken) {
1659
1661
  signingContext.heartbeatToken = heartbeatToken;
@@ -1669,17 +1671,16 @@ var GateClient = class {
1669
1671
  };
1670
1672
  }
1671
1673
  let body = {
1674
+ tenantId: this.config.tenantId,
1672
1675
  requestId,
1673
1676
  timestampMs,
1674
1677
  txIntent,
1675
1678
  signingContext,
1676
1679
  // Add SDK info (required by Hot Path validation)
1677
- // Note: Must match Python SDK name for consistent canonical JSON
1678
1680
  sdk: {
1679
1681
  name: "gate-sdk",
1680
1682
  version: "0.1.0"
1681
1683
  },
1682
- // Add mode and connection failure strategy
1683
1684
  mode: requestMode,
1684
1685
  onConnectionFailure: this.onConnectionFailure
1685
1686
  };
@@ -1709,15 +1710,6 @@ var GateClient = class {
1709
1710
  });
1710
1711
  headers = { ...hmacHeaders };
1711
1712
  body.__canonicalJson = canonicalBodyJson;
1712
- const debugHeaders = {};
1713
- Object.entries(headers).forEach(([key, value]) => {
1714
- if (key.toLowerCase().includes("signature")) {
1715
- debugHeaders[key] = value.substring(0, 8) + "...";
1716
- } else {
1717
- debugHeaders[key] = value;
1718
- }
1719
- });
1720
- console.error("[GATE CLIENT DEBUG] HMAC headers prepared:", JSON.stringify(debugHeaders, null, 2));
1721
1713
  } else if (this.apiKeyAuth) {
1722
1714
  const apiKeyHeaders = this.apiKeyAuth.createHeaders({
1723
1715
  tenantId: this.config.tenantId,
@@ -1725,7 +1717,6 @@ var GateClient = class {
1725
1717
  requestId
1726
1718
  });
1727
1719
  headers = { ...apiKeyHeaders };
1728
- console.error("[GATE CLIENT DEBUG] API key headers prepared:", JSON.stringify(headers, null, 2));
1729
1720
  } else {
1730
1721
  throw new Error("No authentication configured");
1731
1722
  }
@@ -1859,6 +1850,7 @@ var GateClient = class {
1859
1850
  tenantId: this.config.tenantId,
1860
1851
  mode: requestMode
1861
1852
  });
1853
+ console.warn("[GATE SDK] X-BlockIntel-Degraded: true (reason: fail_open)");
1862
1854
  this.metrics.recordRequest("FAIL_OPEN", Date.now() - startTime);
1863
1855
  return {
1864
1856
  decision: "ALLOW",
@@ -1890,6 +1882,10 @@ var GateClient = class {
1890
1882
  }
1891
1883
  throw error;
1892
1884
  }
1885
+ if (error instanceof GateError && error.code === "RATE_LIMITED" /* RATE_LIMITED */) {
1886
+ console.warn("[GATE SDK] X-BlockIntel-Degraded: true (reason: 429)");
1887
+ throw error;
1888
+ }
1893
1889
  if (error instanceof BlockIntelBlockedError || error instanceof BlockIntelStepUpRequiredError) {
1894
1890
  throw error;
1895
1891
  }
@@ -1902,6 +1898,7 @@ var GateClient = class {
1902
1898
  */
1903
1899
  handleFailSafe(mode, error, requestId) {
1904
1900
  if (mode === "ALLOW_ON_TIMEOUT") {
1901
+ console.warn("[GATE SDK] X-BlockIntel-Degraded: true (reason: fail_safe_allow)");
1905
1902
  return {
1906
1903
  decision: "ALLOW",
1907
1904
  reasonCodes: ["FAIL_SAFE_ALLOW"],
@@ -1912,6 +1909,7 @@ var GateClient = class {
1912
1909
  return null;
1913
1910
  }
1914
1911
  if (mode === "BLOCK_ON_ANOMALY") {
1912
+ console.warn("[GATE SDK] X-BlockIntel-Degraded: true (reason: fail_safe_allow)");
1915
1913
  return {
1916
1914
  decision: "ALLOW",
1917
1915
  reasonCodes: ["FAIL_SAFE_ALLOW"],