@driftgard/node 1.13.0 → 1.15.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.
@@ -19,6 +19,7 @@ export interface ControlPackCacheConfig {
19
19
  version?: number;
20
20
  error?: string;
21
21
  stale?: boolean;
22
+ orgSuspended?: boolean;
22
23
  }) => void;
23
24
  }
24
25
  export declare class ControlPackCache {
@@ -29,6 +30,8 @@ export declare class ControlPackCache {
29
30
  private lastRefreshedAt;
30
31
  private refreshTimer;
31
32
  private stale;
33
+ private _orgSuspended;
34
+ private _orgStatus;
32
35
  constructor(config: ControlPackCacheConfig);
33
36
  /** Fetch the active control pack. Must be called before evaluating. */
34
37
  init(): Promise<void>;
@@ -43,6 +46,10 @@ export declare class ControlPackCache {
43
46
  stale: boolean;
44
47
  last_refreshed_at: string | null;
45
48
  };
49
+ /** Check if the org is suspended/cancelled. */
50
+ isOrgSuspended(): boolean;
51
+ /** Get the org status if suspended. */
52
+ getOrgStatus(): string | null;
46
53
  /** Refresh the control pack from the API. */
47
54
  private refresh;
48
55
  }
@@ -16,6 +16,8 @@ class ControlPackCache {
16
16
  this.lastRefreshedAt = null;
17
17
  this.refreshTimer = null;
18
18
  this.stale = false;
19
+ this._orgSuspended = false;
20
+ this._orgStatus = null;
19
21
  this.config = config;
20
22
  }
21
23
  /** Fetch the active control pack. Must be called before evaluating. */
@@ -56,6 +58,14 @@ class ControlPackCache {
56
58
  last_refreshed_at: this.lastRefreshedAt,
57
59
  };
58
60
  }
61
+ /** Check if the org is suspended/cancelled. */
62
+ isOrgSuspended() {
63
+ return this._orgSuspended;
64
+ }
65
+ /** Get the org status if suspended. */
66
+ getOrgStatus() {
67
+ return this._orgStatus;
68
+ }
59
69
  /** Refresh the control pack from the API. */
60
70
  async refresh() {
61
71
  const { apiKey, baseUrl, projectId, timeout = 10000 } = this.config;
@@ -69,6 +79,20 @@ class ControlPackCache {
69
79
  },
70
80
  signal: controller.signal,
71
81
  });
82
+ // Detect org suspension (403 with org_suspended error)
83
+ if (res.status === 403) {
84
+ const payload = await res.json().catch(() => ({}));
85
+ if (payload?.error === "org_suspended") {
86
+ this._orgSuspended = true;
87
+ this._orgStatus = String(payload?.org_status || "suspended");
88
+ this.config.onRefresh?.({
89
+ success: false,
90
+ error: String(payload?.message || "Organisation suspended"),
91
+ orgSuspended: true,
92
+ });
93
+ throw new Error(String(payload?.message || "Your organisation has been suspended. Contact support."));
94
+ }
95
+ }
72
96
  if (!res.ok) {
73
97
  throw new Error(`HTTP ${res.status}: ${res.statusText}`);
74
98
  }
@@ -82,6 +106,9 @@ class ControlPackCache {
82
106
  this.controlPackVersion = Number(cp.version || 0);
83
107
  this.lastRefreshedAt = new Date().toISOString();
84
108
  this.stale = false;
109
+ // Clear suspension flag on successful refresh (org reactivated)
110
+ this._orgSuspended = false;
111
+ this._orgStatus = null;
85
112
  this.config.onRefresh?.({
86
113
  success: true,
87
114
  version: this.controlPackVersion,
@@ -90,7 +117,7 @@ class ControlPackCache {
90
117
  catch (e) {
91
118
  const msg = e instanceof Error ? e.message : String(e);
92
119
  // If we have a cached pack, mark as stale but keep using it
93
- if (this.controlPack) {
120
+ if (this.controlPack && !this._orgSuspended) {
94
121
  this.stale = true;
95
122
  this.config.onRefresh?.({
96
123
  success: false,
@@ -99,7 +126,7 @@ class ControlPackCache {
99
126
  version: this.controlPackVersion,
100
127
  });
101
128
  }
102
- else {
129
+ else if (!this._orgSuspended) {
103
130
  // No cached pack — this is fatal for local mode
104
131
  this.config.onRefresh?.({
105
132
  success: false,
package/dist/errors.d.ts CHANGED
@@ -18,3 +18,7 @@ export declare class ChainDepthExceededError extends DriftgardError {
18
18
  max: number;
19
19
  constructor(message?: string, depth?: number, max?: number);
20
20
  }
21
+ export declare class OrgSuspendedError extends DriftgardError {
22
+ orgStatus: string;
23
+ constructor(message?: string, orgStatus?: string);
24
+ }
package/dist/errors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ChainDepthExceededError = exports.FeatureNotAvailableError = exports.RateLimitError = exports.AuthError = exports.DriftgardError = void 0;
3
+ exports.OrgSuspendedError = exports.ChainDepthExceededError = exports.FeatureNotAvailableError = exports.RateLimitError = exports.AuthError = exports.DriftgardError = void 0;
4
4
  class DriftgardError extends Error {
5
5
  constructor(message, status, code) {
6
6
  super(message);
@@ -41,3 +41,11 @@ class ChainDepthExceededError extends DriftgardError {
41
41
  }
42
42
  }
43
43
  exports.ChainDepthExceededError = ChainDepthExceededError;
44
+ class OrgSuspendedError extends DriftgardError {
45
+ constructor(message = "Your organisation has been suspended. Contact support.", orgStatus = "suspended") {
46
+ super(message, 403, "org_suspended");
47
+ this.name = "OrgSuspendedError";
48
+ this.orgStatus = orgStatus;
49
+ }
50
+ }
51
+ exports.OrgSuspendedError = OrgSuspendedError;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { DriftgardConfig, EvaluateRequest, EvaluateResponse, ToolCallRequest, OutcomeRequest } from "./types";
2
2
  export { DriftgardConfig, EvaluateRequest, EvaluateResponse, CircuitBreakerConfig, ToolCallRequest, OutcomeRequest } from "./types";
3
3
  export { Violation, EvaluationResult, FallbackResponse, HitlInfo } from "./types";
4
- export { DriftgardError, AuthError, RateLimitError, FeatureNotAvailableError, ChainDepthExceededError } from "./errors";
4
+ export { DriftgardError, AuthError, RateLimitError, FeatureNotAvailableError, ChainDepthExceededError, OrgSuspendedError } from "./errors";
5
5
  export { evaluateLocal } from "./local-evaluator";
6
6
  export { ControlPackCache } from "./control-pack-cache";
7
7
  type CircuitState = "closed" | "open" | "half-open";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Driftgard = exports.ControlPackCache = exports.evaluateLocal = exports.ChainDepthExceededError = exports.FeatureNotAvailableError = exports.RateLimitError = exports.AuthError = exports.DriftgardError = void 0;
3
+ exports.Driftgard = exports.ControlPackCache = exports.evaluateLocal = exports.OrgSuspendedError = exports.ChainDepthExceededError = exports.FeatureNotAvailableError = exports.RateLimitError = exports.AuthError = exports.DriftgardError = void 0;
4
4
  const errors_1 = require("./errors");
5
5
  const local_evaluator_1 = require("./local-evaluator");
6
6
  const control_pack_cache_1 = require("./control-pack-cache");
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "AuthError", { enumerable: true, get: function ()
10
10
  Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_2.RateLimitError; } });
11
11
  Object.defineProperty(exports, "FeatureNotAvailableError", { enumerable: true, get: function () { return errors_2.FeatureNotAvailableError; } });
12
12
  Object.defineProperty(exports, "ChainDepthExceededError", { enumerable: true, get: function () { return errors_2.ChainDepthExceededError; } });
13
+ Object.defineProperty(exports, "OrgSuspendedError", { enumerable: true, get: function () { return errors_2.OrgSuspendedError; } });
13
14
  var local_evaluator_2 = require("./local-evaluator");
14
15
  Object.defineProperty(exports, "evaluateLocal", { enumerable: true, get: function () { return local_evaluator_2.evaluateLocal; } });
15
16
  var control_pack_cache_2 = require("./control-pack-cache");
@@ -86,6 +87,10 @@ class Driftgard {
86
87
  throw new Error("Local mode not configured");
87
88
  if (!this.initialized)
88
89
  throw new Error("Call init() before evaluate() in local mode");
90
+ // Check if org is suspended — block all evaluations
91
+ if (this.cpCache.isOrgSuspended()) {
92
+ throw new errors_1.OrgSuspendedError("Your organisation has been suspended. Contact support.", this.cpCache.getOrgStatus() || "suspended");
93
+ }
89
94
  const cp = this.cpCache.getControlPack();
90
95
  const meta = this.cpCache.getMeta();
91
96
  const timestamp = req.timestamp || new Date().toISOString();
@@ -207,9 +212,10 @@ class Driftgard {
207
212
  return result;
208
213
  }
209
214
  catch (e) {
210
- // Auth, rate limit, chain depth, feature errors are real errors — don't trigger circuit breaker
215
+ // Auth, rate limit, chain depth, feature, org suspended errors are real errors — don't trigger circuit breaker
211
216
  if (e instanceof errors_1.AuthError || e instanceof errors_1.RateLimitError ||
212
- e instanceof errors_1.ChainDepthExceededError || e instanceof errors_1.FeatureNotAvailableError) {
217
+ e instanceof errors_1.ChainDepthExceededError || e instanceof errors_1.FeatureNotAvailableError ||
218
+ e instanceof errors_1.OrgSuspendedError) {
213
219
  throw e;
214
220
  }
215
221
  // Network/server error — increment circuit breaker
@@ -344,6 +350,9 @@ class Driftgard {
344
350
  if (payload?.error === "feature_not_available") {
345
351
  throw new errors_1.FeatureNotAvailableError(payload.message, payload.tier);
346
352
  }
353
+ if (payload?.error === "org_suspended") {
354
+ throw new errors_1.OrgSuspendedError(payload?.message, payload?.org_status || "suspended");
355
+ }
347
356
  throw new errors_1.DriftgardError(payload?.message || "Forbidden", 403, "forbidden");
348
357
  }
349
358
  // Retry on 5xx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftgard/node",
3
- "version": "1.13.0",
3
+ "version": "1.15.0",
4
4
  "description": "Official DriftGard Node.js SDK — evaluate LLM interactions against your compliance policy",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,6 +10,7 @@
10
10
  ],
11
11
  "scripts": {
12
12
  "build": "tsc && rm -rf dist/wasm && cp -r src/wasm dist/wasm",
13
+ "test": "jest --verbose",
13
14
  "prepublishOnly": "npm run build"
14
15
  },
15
16
  "keywords": [
@@ -33,7 +34,10 @@
33
34
  "node": ">=18.0.0"
34
35
  },
35
36
  "devDependencies": {
37
+ "@types/jest": "^30.0.0",
36
38
  "@types/node": "^25.6.0",
39
+ "jest": "^30.3.0",
40
+ "ts-jest": "^29.4.9",
37
41
  "typescript": "^5.0.0"
38
42
  }
39
43
  }