guardian-risk 0.2.0 → 0.2.1

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
@@ -6,8 +6,6 @@ Configurable risk decision engine for TypeScript. Evaluate signals against rules
6
6
 
7
7
  ```bash
8
8
  npm install guardian-risk
9
- # or
10
- pnpm add guardian-risk
11
9
  ```
12
10
 
13
11
  ## Usage
@@ -36,7 +34,41 @@ console.log(report.score); // 35
36
34
  console.log(report.level); // MEDIUM
37
35
  ```
38
36
 
39
- ## Plugins
37
+ ## Official plugins
38
+
39
+ Install **core first**, then add only the plugins you need:
40
+
41
+ | Package | Install | Purpose |
42
+ |---------|---------|---------|
43
+ | **guardian-risk** (this) | `npm i guardian-risk` | Core engine |
44
+ | guardian-risk-express | `npm i guardian-risk-express` | Express request signals |
45
+ | guardian-risk-browser | `npm i guardian-risk-browser` | Browser behavioral signals |
46
+ | guardian-risk-redis | `npm i guardian-risk-redis` | Redis session counters |
47
+ | guardian-risk-vpn | `npm i guardian-risk-vpn` | VPN / proxy / Tor detection |
48
+ | guardian-risk-logger | `npm i guardian-risk-logger` | Audit logging |
49
+
50
+ ```bash
51
+ # Example: Express + VPN + Logger stack
52
+ npm install guardian-risk guardian-risk-express guardian-risk-vpn guardian-risk-logger
53
+ ```
54
+
55
+ ```typescript
56
+ import { Guardian } from 'guardian-risk';
57
+ import { expressPlugin } from 'guardian-risk-express';
58
+ import { vpnPlugin } from 'guardian-risk-vpn';
59
+ import { loggerPlugin, analyzeAndLog } from 'guardian-risk-logger';
60
+
61
+ const guardian = new Guardian()
62
+ .use(expressPlugin())
63
+ .use(vpnPlugin())
64
+ .use(loggerPlugin());
65
+
66
+ analyzeAndLog(guardian);
67
+ ```
68
+
69
+ > Plugin packages are early stubs — APIs may change before `1.0.0`.
70
+
71
+ ## Plugins API
40
72
 
41
73
  ```typescript
42
74
  import type { Plugin } from 'guardian-risk';
@@ -62,6 +94,21 @@ new Guardian().use(myPlugin);
62
94
  | `guardian.reset()` | Clear signals (rules + plugins persist) |
63
95
  | `guardian.getInstalledPlugins()` | List installed plugin names |
64
96
 
97
+ ## Security
98
+
99
+ - **Zero runtime dependencies** — minimal supply chain risk
100
+ - **No install scripts** — nothing runs on `npm install`
101
+ - Prototype pollution protection on signal keys
102
+ - Rule `when()` errors isolated — engine stays stable
103
+ - Score bounds: ±10,000 per rule, ±1,000,000 total
104
+ - Plugin `install()` failures throw `PluginInstallError` without registering
105
+ - See [SECURITY.md](SECURITY.md) for vulnerability reporting
106
+
107
+ ## Links
108
+
109
+ - [GitHub monorepo](https://github.com/himanshu6306singh/guardian-risk)
110
+ - [All packages guide](https://github.com/himanshu6306singh/guardian-risk/blob/main/ECOSYSTEM.md)
111
+
65
112
  ## License
66
113
 
67
114
  MIT
package/dist/index.cjs CHANGED
@@ -10,6 +10,18 @@ var DEFAULT_RISK_LEVELS = [
10
10
  { max: Infinity, level: "CRITICAL" }
11
11
  ];
12
12
 
13
+ // src/constants/security.ts
14
+ var MAX_RULES = 1e3;
15
+ var MAX_SIGNALS = 1e3;
16
+ var MAX_KEY_LENGTH = 256;
17
+ var MAX_RULE_SCORE = 1e4;
18
+ var MAX_TOTAL_SCORE = 1e6;
19
+ var BLOCKED_SIGNAL_KEYS = /* @__PURE__ */ new Set([
20
+ "__proto__",
21
+ "constructor",
22
+ "prototype"
23
+ ]);
24
+
13
25
  // src/utils/resolveLevel.ts
14
26
  function resolveLevel(score, thresholds = DEFAULT_RISK_LEVELS) {
15
27
  for (const threshold of thresholds) {
@@ -49,7 +61,13 @@ var RuleEvaluator = class {
49
61
  evaluate(rules, signals) {
50
62
  const matched = [];
51
63
  for (const rule of rules) {
52
- if (rule.when(signals)) {
64
+ let isMatched = false;
65
+ try {
66
+ isMatched = rule.when(signals);
67
+ } catch {
68
+ isMatched = false;
69
+ }
70
+ if (isMatched) {
53
71
  matched.push({
54
72
  id: rule.id,
55
73
  name: rule.name,
@@ -65,10 +83,20 @@ var RuleEvaluator = class {
65
83
  // src/score/ScoreCalculator.ts
66
84
  var ScoreCalculator = class {
67
85
  /**
68
- * Sum scores from all matched rules.
86
+ * Sum scores from all matched rules, clamped to a safe maximum.
69
87
  */
70
88
  calculate(matchedRules) {
71
- return matchedRules.reduce((total, rule) => total + rule.score, 0);
89
+ const total = matchedRules.reduce((sum, rule) => sum + rule.score, 0);
90
+ if (!Number.isFinite(total)) {
91
+ return 0;
92
+ }
93
+ if (total > MAX_TOTAL_SCORE) {
94
+ return MAX_TOTAL_SCORE;
95
+ }
96
+ if (total < -MAX_TOTAL_SCORE) {
97
+ return -MAX_TOTAL_SCORE;
98
+ }
99
+ return total;
72
100
  }
73
101
  };
74
102
  function validateSignalValue(value) {
@@ -78,6 +106,74 @@ function validateSignalValue(value) {
78
106
  const type = typeof value;
79
107
  return type === "string" || type === "number" || type === "boolean";
80
108
  }
109
+ function validateSignalKey(key) {
110
+ if (typeof key !== "string" || key.length === 0) {
111
+ throw new TypeError("Signal key must be a non-empty string");
112
+ }
113
+ if (key.length > MAX_KEY_LENGTH) {
114
+ throw new TypeError(`Signal key exceeds maximum length of ${MAX_KEY_LENGTH}`);
115
+ }
116
+ if (BLOCKED_SIGNAL_KEYS.has(key)) {
117
+ throw new TypeError(`Signal key "${key}" is not allowed`);
118
+ }
119
+ }
120
+ function validateRuleInput(input) {
121
+ if (typeof input.name !== "string" || input.name.trim().length === 0) {
122
+ throw new TypeError("Rule name must be a non-empty string");
123
+ }
124
+ if (input.name.length > MAX_KEY_LENGTH) {
125
+ throw new TypeError(`Rule name exceeds maximum length of ${MAX_KEY_LENGTH}`);
126
+ }
127
+ if (typeof input.when !== "function") {
128
+ throw new TypeError("Rule when must be a function");
129
+ }
130
+ if (typeof input.score !== "number" || !Number.isFinite(input.score)) {
131
+ throw new TypeError("Rule score must be a finite number");
132
+ }
133
+ if (Math.abs(input.score) > MAX_RULE_SCORE) {
134
+ throw new TypeError(`Rule score must be between -${MAX_RULE_SCORE} and ${MAX_RULE_SCORE}`);
135
+ }
136
+ if (input.reason !== void 0) {
137
+ if (typeof input.reason !== "string") {
138
+ throw new TypeError("Rule reason must be a string");
139
+ }
140
+ if (input.reason.length > MAX_KEY_LENGTH) {
141
+ throw new TypeError(`Rule reason exceeds maximum length of ${MAX_KEY_LENGTH}`);
142
+ }
143
+ }
144
+ if (input.description !== void 0) {
145
+ if (typeof input.description !== "string") {
146
+ throw new TypeError("Rule description must be a string");
147
+ }
148
+ if (input.description.length > MAX_KEY_LENGTH) {
149
+ throw new TypeError(`Rule description exceeds maximum length of ${MAX_KEY_LENGTH}`);
150
+ }
151
+ }
152
+ }
153
+ function validatePlugin(plugin) {
154
+ if (typeof plugin.name !== "string" || plugin.name.trim().length === 0) {
155
+ throw new TypeError("Plugin name must be a non-empty string");
156
+ }
157
+ if (plugin.name.length > MAX_KEY_LENGTH) {
158
+ throw new TypeError(`Plugin name exceeds maximum length of ${MAX_KEY_LENGTH}`);
159
+ }
160
+ if (typeof plugin.install !== "function") {
161
+ throw new TypeError("Plugin install must be a function");
162
+ }
163
+ }
164
+ function validateRiskLevels(levels) {
165
+ if (levels.length === 0) {
166
+ throw new TypeError("Risk levels must contain at least one threshold");
167
+ }
168
+ for (const threshold of levels) {
169
+ if (typeof threshold.level !== "string" || threshold.level.trim().length === 0) {
170
+ throw new TypeError("Risk level label must be a non-empty string");
171
+ }
172
+ if (typeof threshold.max !== "number" || !Number.isFinite(threshold.max) && threshold.max !== Infinity) {
173
+ throw new TypeError("Risk level max must be a finite number or Infinity");
174
+ }
175
+ }
176
+ }
81
177
  function generateId() {
82
178
  return crypto.randomUUID();
83
179
  }
@@ -89,11 +185,15 @@ var SignalStore = class {
89
185
  * Set a signal value. Overwrites any existing value for the key.
90
186
  */
91
187
  set(key, value) {
188
+ validateSignalKey(key);
92
189
  if (!validateSignalValue(value)) {
93
190
  throw new TypeError(
94
191
  `Invalid signal value for "${key}": signals must be string, number, boolean, or null`
95
192
  );
96
193
  }
194
+ if (!this.signals.has(key) && this.signals.size >= MAX_SIGNALS) {
195
+ throw new RangeError(`Cannot exceed maximum of ${MAX_SIGNALS} signals`);
196
+ }
97
197
  this.signals.set(key, value);
98
198
  return this;
99
199
  }
@@ -113,7 +213,7 @@ var SignalStore = class {
113
213
  * Returns a frozen snapshot of all signals.
114
214
  */
115
215
  getAll() {
116
- const snapshot = {};
216
+ const snapshot = /* @__PURE__ */ Object.create(null);
117
217
  for (const [key, value] of this.signals) {
118
218
  snapshot[key] = value;
119
219
  }
@@ -140,6 +240,9 @@ var RiskEngine = class {
140
240
  * Register a rule for evaluation.
141
241
  */
142
242
  addRule(rule) {
243
+ if (this.rules.length >= MAX_RULES) {
244
+ throw new RangeError(`Cannot exceed maximum of ${MAX_RULES} rules`);
245
+ }
143
246
  this.rules.push(rule);
144
247
  }
145
248
  /**
@@ -165,6 +268,7 @@ var RuleBuilder = class {
165
268
  * Create a new rule from input configuration.
166
269
  */
167
270
  static create(input) {
271
+ validateRuleInput(input);
168
272
  const rule = {
169
273
  id: generateId(),
170
274
  name: input.name,
@@ -178,6 +282,13 @@ var RuleBuilder = class {
178
282
  };
179
283
 
180
284
  // src/plugins/PluginRegistry.ts
285
+ var PluginInstallError = class extends Error {
286
+ constructor(pluginName, cause) {
287
+ const message = cause instanceof Error ? cause.message : "Unknown plugin install error";
288
+ super(`Plugin "${pluginName}" failed to install: ${message}`);
289
+ this.name = "PluginInstallError";
290
+ }
291
+ };
181
292
  var PluginAlreadyInstalledError = class extends Error {
182
293
  constructor(pluginName) {
183
294
  super(`Plugin "${pluginName}" is already installed`);
@@ -191,10 +302,15 @@ var PluginRegistry = class {
191
302
  * Each plugin name may only be installed once per Guardian instance.
192
303
  */
193
304
  install(plugin, guardian) {
305
+ validatePlugin(plugin);
194
306
  if (this.installed.has(plugin.name)) {
195
307
  throw new PluginAlreadyInstalledError(plugin.name);
196
308
  }
197
- plugin.install(guardian);
309
+ try {
310
+ plugin.install(guardian);
311
+ } catch (error) {
312
+ throw new PluginInstallError(plugin.name, error);
313
+ }
198
314
  this.installed.add(plugin.name);
199
315
  }
200
316
  /**
@@ -218,6 +334,9 @@ var Guardian = class {
218
334
  pluginRegistry = new PluginRegistry();
219
335
  constructor(config = {}) {
220
336
  const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;
337
+ if (config.levels !== void 0) {
338
+ validateRiskLevels(config.levels);
339
+ }
221
340
  this.signalStore = new SignalStore();
222
341
  const deps = {
223
342
  signalStore: this.signalStore,
@@ -273,6 +392,7 @@ var Guardian = class {
273
392
  exports.DEFAULT_RISK_LEVELS = DEFAULT_RISK_LEVELS;
274
393
  exports.Guardian = Guardian;
275
394
  exports.PluginAlreadyInstalledError = PluginAlreadyInstalledError;
395
+ exports.PluginInstallError = PluginInstallError;
276
396
  exports.PluginRegistry = PluginRegistry;
277
397
  exports.RiskEngine = RiskEngine;
278
398
  exports.RuleBuilder = RuleBuilder;
@@ -280,5 +400,3 @@ exports.RuleEvaluator = RuleEvaluator;
280
400
  exports.ScoreCalculator = ScoreCalculator;
281
401
  exports.SignalStore = SignalStore;
282
402
  exports.resolveLevel = resolveLevel;
283
- //# sourceMappingURL=index.cjs.map
284
- //# sourceMappingURL=index.cjs.map
package/dist/index.d.cts CHANGED
@@ -6,6 +6,8 @@ interface Plugin {
6
6
  readonly name: string;
7
7
  install(guardian: Guardian): void;
8
8
  }
9
+ /** Public alias for {@link Plugin}. */
10
+ type GuardianPlugin = Plugin;
9
11
 
10
12
  /** Primitive signal value types supported by Guardian. */
11
13
  type SignalValue = string | number | boolean | null;
@@ -121,7 +123,7 @@ declare class RuleEvaluator {
121
123
  */
122
124
  declare class ScoreCalculator {
123
125
  /**
124
- * Sum scores from all matched rules.
126
+ * Sum scores from all matched rules, clamped to a safe maximum.
125
127
  */
126
128
  calculate(matchedRules: readonly MatchedRule[]): number;
127
129
  }
@@ -182,6 +184,12 @@ declare class RiskEngine {
182
184
  analyze(): RiskReport;
183
185
  }
184
186
 
187
+ /**
188
+ * Thrown when a plugin's install() callback fails.
189
+ */
190
+ declare class PluginInstallError extends Error {
191
+ constructor(pluginName: string, cause: unknown);
192
+ }
185
193
  /**
186
194
  * Thrown when attempting to register a plugin that is already installed.
187
195
  */
@@ -227,4 +235,4 @@ declare function resolveLevel(score: number, thresholds?: readonly RiskLevelThre
227
235
  /** Default risk level thresholds used when none are configured. */
228
236
  declare const DEFAULT_RISK_LEVELS: readonly RiskLevelThreshold[];
229
237
 
230
- export { type CreateRuleInput, DEFAULT_RISK_LEVELS, Guardian, type GuardianConfig, type MatchedRule, type Plugin, PluginAlreadyInstalledError, PluginRegistry, RiskEngine, type RiskEngineDependencies, type RiskLevelThreshold, type RiskReport, type Rule, RuleBuilder, RuleEvaluator, ScoreCalculator, type SignalDefinition, type SignalMap, SignalStore, type SignalValue, resolveLevel };
238
+ export { type CreateRuleInput, DEFAULT_RISK_LEVELS, Guardian, type GuardianConfig, type GuardianPlugin, type MatchedRule, type Plugin, PluginAlreadyInstalledError, PluginInstallError, PluginRegistry, RiskEngine, type RiskEngineDependencies, type RiskLevelThreshold, type RiskReport, type Rule, RuleBuilder, RuleEvaluator, ScoreCalculator, type SignalDefinition, type SignalMap, SignalStore, type SignalValue, resolveLevel };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,8 @@ interface Plugin {
6
6
  readonly name: string;
7
7
  install(guardian: Guardian): void;
8
8
  }
9
+ /** Public alias for {@link Plugin}. */
10
+ type GuardianPlugin = Plugin;
9
11
 
10
12
  /** Primitive signal value types supported by Guardian. */
11
13
  type SignalValue = string | number | boolean | null;
@@ -121,7 +123,7 @@ declare class RuleEvaluator {
121
123
  */
122
124
  declare class ScoreCalculator {
123
125
  /**
124
- * Sum scores from all matched rules.
126
+ * Sum scores from all matched rules, clamped to a safe maximum.
125
127
  */
126
128
  calculate(matchedRules: readonly MatchedRule[]): number;
127
129
  }
@@ -182,6 +184,12 @@ declare class RiskEngine {
182
184
  analyze(): RiskReport;
183
185
  }
184
186
 
187
+ /**
188
+ * Thrown when a plugin's install() callback fails.
189
+ */
190
+ declare class PluginInstallError extends Error {
191
+ constructor(pluginName: string, cause: unknown);
192
+ }
185
193
  /**
186
194
  * Thrown when attempting to register a plugin that is already installed.
187
195
  */
@@ -227,4 +235,4 @@ declare function resolveLevel(score: number, thresholds?: readonly RiskLevelThre
227
235
  /** Default risk level thresholds used when none are configured. */
228
236
  declare const DEFAULT_RISK_LEVELS: readonly RiskLevelThreshold[];
229
237
 
230
- export { type CreateRuleInput, DEFAULT_RISK_LEVELS, Guardian, type GuardianConfig, type MatchedRule, type Plugin, PluginAlreadyInstalledError, PluginRegistry, RiskEngine, type RiskEngineDependencies, type RiskLevelThreshold, type RiskReport, type Rule, RuleBuilder, RuleEvaluator, ScoreCalculator, type SignalDefinition, type SignalMap, SignalStore, type SignalValue, resolveLevel };
238
+ export { type CreateRuleInput, DEFAULT_RISK_LEVELS, Guardian, type GuardianConfig, type GuardianPlugin, type MatchedRule, type Plugin, PluginAlreadyInstalledError, PluginInstallError, PluginRegistry, RiskEngine, type RiskEngineDependencies, type RiskLevelThreshold, type RiskReport, type Rule, RuleBuilder, RuleEvaluator, ScoreCalculator, type SignalDefinition, type SignalMap, SignalStore, type SignalValue, resolveLevel };
package/dist/index.js CHANGED
@@ -8,6 +8,18 @@ var DEFAULT_RISK_LEVELS = [
8
8
  { max: Infinity, level: "CRITICAL" }
9
9
  ];
10
10
 
11
+ // src/constants/security.ts
12
+ var MAX_RULES = 1e3;
13
+ var MAX_SIGNALS = 1e3;
14
+ var MAX_KEY_LENGTH = 256;
15
+ var MAX_RULE_SCORE = 1e4;
16
+ var MAX_TOTAL_SCORE = 1e6;
17
+ var BLOCKED_SIGNAL_KEYS = /* @__PURE__ */ new Set([
18
+ "__proto__",
19
+ "constructor",
20
+ "prototype"
21
+ ]);
22
+
11
23
  // src/utils/resolveLevel.ts
12
24
  function resolveLevel(score, thresholds = DEFAULT_RISK_LEVELS) {
13
25
  for (const threshold of thresholds) {
@@ -47,7 +59,13 @@ var RuleEvaluator = class {
47
59
  evaluate(rules, signals) {
48
60
  const matched = [];
49
61
  for (const rule of rules) {
50
- if (rule.when(signals)) {
62
+ let isMatched = false;
63
+ try {
64
+ isMatched = rule.when(signals);
65
+ } catch {
66
+ isMatched = false;
67
+ }
68
+ if (isMatched) {
51
69
  matched.push({
52
70
  id: rule.id,
53
71
  name: rule.name,
@@ -63,10 +81,20 @@ var RuleEvaluator = class {
63
81
  // src/score/ScoreCalculator.ts
64
82
  var ScoreCalculator = class {
65
83
  /**
66
- * Sum scores from all matched rules.
84
+ * Sum scores from all matched rules, clamped to a safe maximum.
67
85
  */
68
86
  calculate(matchedRules) {
69
- return matchedRules.reduce((total, rule) => total + rule.score, 0);
87
+ const total = matchedRules.reduce((sum, rule) => sum + rule.score, 0);
88
+ if (!Number.isFinite(total)) {
89
+ return 0;
90
+ }
91
+ if (total > MAX_TOTAL_SCORE) {
92
+ return MAX_TOTAL_SCORE;
93
+ }
94
+ if (total < -MAX_TOTAL_SCORE) {
95
+ return -MAX_TOTAL_SCORE;
96
+ }
97
+ return total;
70
98
  }
71
99
  };
72
100
  function validateSignalValue(value) {
@@ -76,6 +104,74 @@ function validateSignalValue(value) {
76
104
  const type = typeof value;
77
105
  return type === "string" || type === "number" || type === "boolean";
78
106
  }
107
+ function validateSignalKey(key) {
108
+ if (typeof key !== "string" || key.length === 0) {
109
+ throw new TypeError("Signal key must be a non-empty string");
110
+ }
111
+ if (key.length > MAX_KEY_LENGTH) {
112
+ throw new TypeError(`Signal key exceeds maximum length of ${MAX_KEY_LENGTH}`);
113
+ }
114
+ if (BLOCKED_SIGNAL_KEYS.has(key)) {
115
+ throw new TypeError(`Signal key "${key}" is not allowed`);
116
+ }
117
+ }
118
+ function validateRuleInput(input) {
119
+ if (typeof input.name !== "string" || input.name.trim().length === 0) {
120
+ throw new TypeError("Rule name must be a non-empty string");
121
+ }
122
+ if (input.name.length > MAX_KEY_LENGTH) {
123
+ throw new TypeError(`Rule name exceeds maximum length of ${MAX_KEY_LENGTH}`);
124
+ }
125
+ if (typeof input.when !== "function") {
126
+ throw new TypeError("Rule when must be a function");
127
+ }
128
+ if (typeof input.score !== "number" || !Number.isFinite(input.score)) {
129
+ throw new TypeError("Rule score must be a finite number");
130
+ }
131
+ if (Math.abs(input.score) > MAX_RULE_SCORE) {
132
+ throw new TypeError(`Rule score must be between -${MAX_RULE_SCORE} and ${MAX_RULE_SCORE}`);
133
+ }
134
+ if (input.reason !== void 0) {
135
+ if (typeof input.reason !== "string") {
136
+ throw new TypeError("Rule reason must be a string");
137
+ }
138
+ if (input.reason.length > MAX_KEY_LENGTH) {
139
+ throw new TypeError(`Rule reason exceeds maximum length of ${MAX_KEY_LENGTH}`);
140
+ }
141
+ }
142
+ if (input.description !== void 0) {
143
+ if (typeof input.description !== "string") {
144
+ throw new TypeError("Rule description must be a string");
145
+ }
146
+ if (input.description.length > MAX_KEY_LENGTH) {
147
+ throw new TypeError(`Rule description exceeds maximum length of ${MAX_KEY_LENGTH}`);
148
+ }
149
+ }
150
+ }
151
+ function validatePlugin(plugin) {
152
+ if (typeof plugin.name !== "string" || plugin.name.trim().length === 0) {
153
+ throw new TypeError("Plugin name must be a non-empty string");
154
+ }
155
+ if (plugin.name.length > MAX_KEY_LENGTH) {
156
+ throw new TypeError(`Plugin name exceeds maximum length of ${MAX_KEY_LENGTH}`);
157
+ }
158
+ if (typeof plugin.install !== "function") {
159
+ throw new TypeError("Plugin install must be a function");
160
+ }
161
+ }
162
+ function validateRiskLevels(levels) {
163
+ if (levels.length === 0) {
164
+ throw new TypeError("Risk levels must contain at least one threshold");
165
+ }
166
+ for (const threshold of levels) {
167
+ if (typeof threshold.level !== "string" || threshold.level.trim().length === 0) {
168
+ throw new TypeError("Risk level label must be a non-empty string");
169
+ }
170
+ if (typeof threshold.max !== "number" || !Number.isFinite(threshold.max) && threshold.max !== Infinity) {
171
+ throw new TypeError("Risk level max must be a finite number or Infinity");
172
+ }
173
+ }
174
+ }
79
175
  function generateId() {
80
176
  return randomUUID();
81
177
  }
@@ -87,11 +183,15 @@ var SignalStore = class {
87
183
  * Set a signal value. Overwrites any existing value for the key.
88
184
  */
89
185
  set(key, value) {
186
+ validateSignalKey(key);
90
187
  if (!validateSignalValue(value)) {
91
188
  throw new TypeError(
92
189
  `Invalid signal value for "${key}": signals must be string, number, boolean, or null`
93
190
  );
94
191
  }
192
+ if (!this.signals.has(key) && this.signals.size >= MAX_SIGNALS) {
193
+ throw new RangeError(`Cannot exceed maximum of ${MAX_SIGNALS} signals`);
194
+ }
95
195
  this.signals.set(key, value);
96
196
  return this;
97
197
  }
@@ -111,7 +211,7 @@ var SignalStore = class {
111
211
  * Returns a frozen snapshot of all signals.
112
212
  */
113
213
  getAll() {
114
- const snapshot = {};
214
+ const snapshot = /* @__PURE__ */ Object.create(null);
115
215
  for (const [key, value] of this.signals) {
116
216
  snapshot[key] = value;
117
217
  }
@@ -138,6 +238,9 @@ var RiskEngine = class {
138
238
  * Register a rule for evaluation.
139
239
  */
140
240
  addRule(rule) {
241
+ if (this.rules.length >= MAX_RULES) {
242
+ throw new RangeError(`Cannot exceed maximum of ${MAX_RULES} rules`);
243
+ }
141
244
  this.rules.push(rule);
142
245
  }
143
246
  /**
@@ -163,6 +266,7 @@ var RuleBuilder = class {
163
266
  * Create a new rule from input configuration.
164
267
  */
165
268
  static create(input) {
269
+ validateRuleInput(input);
166
270
  const rule = {
167
271
  id: generateId(),
168
272
  name: input.name,
@@ -176,6 +280,13 @@ var RuleBuilder = class {
176
280
  };
177
281
 
178
282
  // src/plugins/PluginRegistry.ts
283
+ var PluginInstallError = class extends Error {
284
+ constructor(pluginName, cause) {
285
+ const message = cause instanceof Error ? cause.message : "Unknown plugin install error";
286
+ super(`Plugin "${pluginName}" failed to install: ${message}`);
287
+ this.name = "PluginInstallError";
288
+ }
289
+ };
179
290
  var PluginAlreadyInstalledError = class extends Error {
180
291
  constructor(pluginName) {
181
292
  super(`Plugin "${pluginName}" is already installed`);
@@ -189,10 +300,15 @@ var PluginRegistry = class {
189
300
  * Each plugin name may only be installed once per Guardian instance.
190
301
  */
191
302
  install(plugin, guardian) {
303
+ validatePlugin(plugin);
192
304
  if (this.installed.has(plugin.name)) {
193
305
  throw new PluginAlreadyInstalledError(plugin.name);
194
306
  }
195
- plugin.install(guardian);
307
+ try {
308
+ plugin.install(guardian);
309
+ } catch (error) {
310
+ throw new PluginInstallError(plugin.name, error);
311
+ }
196
312
  this.installed.add(plugin.name);
197
313
  }
198
314
  /**
@@ -216,6 +332,9 @@ var Guardian = class {
216
332
  pluginRegistry = new PluginRegistry();
217
333
  constructor(config = {}) {
218
334
  const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;
335
+ if (config.levels !== void 0) {
336
+ validateRiskLevels(config.levels);
337
+ }
219
338
  this.signalStore = new SignalStore();
220
339
  const deps = {
221
340
  signalStore: this.signalStore,
@@ -268,6 +387,4 @@ var Guardian = class {
268
387
  }
269
388
  };
270
389
 
271
- export { DEFAULT_RISK_LEVELS, Guardian, PluginAlreadyInstalledError, PluginRegistry, RiskEngine, RuleBuilder, RuleEvaluator, ScoreCalculator, SignalStore, resolveLevel };
272
- //# sourceMappingURL=index.js.map
273
- //# sourceMappingURL=index.js.map
390
+ export { DEFAULT_RISK_LEVELS, Guardian, PluginAlreadyInstalledError, PluginInstallError, PluginRegistry, RiskEngine, RuleBuilder, RuleEvaluator, ScoreCalculator, SignalStore, resolveLevel };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "guardian-risk",
3
- "version": "0.2.0",
4
- "description": "Configurable risk decision engine using signals, rules, and scoring",
3
+ "version": "0.2.1",
4
+ "description": "Configurable risk decision engine using signals, rules, and scoring. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
@@ -20,7 +20,10 @@
20
20
  },
21
21
  "sideEffects": false,
22
22
  "files": [
23
- "dist",
23
+ "dist/index.js",
24
+ "dist/index.cjs",
25
+ "dist/index.d.ts",
26
+ "dist/index.d.cts",
24
27
  "README.md",
25
28
  "LICENSE"
26
29
  ],
@@ -28,6 +31,7 @@
28
31
  "node": ">=20"
29
32
  },
30
33
  "keywords": [
34
+ "guardian-risk",
31
35
  "risk",
32
36
  "risk-scoring",
33
37
  "rules-engine",
@@ -36,14 +40,27 @@
36
40
  "security",
37
41
  "typescript"
38
42
  ],
43
+ "author": "himanshuusinghh",
39
44
  "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/himanshu6306singh/guardian-risk.git",
48
+ "directory": "packages/core"
49
+ },
50
+ "homepage": "https://github.com/himanshu6306singh/guardian-risk#readme",
51
+ "bugs": {
52
+ "url": "https://github.com/himanshu6306singh/guardian-risk/issues"
53
+ },
54
+ "security": "https://github.com/himanshu6306singh/guardian-risk/security/policy",
55
+ "funding": "https://github.com/sponsors/himanshu6306singh",
40
56
  "publishConfig": {
41
57
  "access": "public"
42
58
  },
43
59
  "devDependencies": {
60
+ "@vitest/coverage-v8": "^4.1.9",
44
61
  "tsup": "^8.3.5",
45
62
  "typescript": "^5.7.2",
46
- "vitest": "^2.1.8"
63
+ "vitest": "^4.1.9"
47
64
  },
48
65
  "scripts": {
49
66
  "build": "tsup",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/constants/defaults.ts","../src/utils/resolveLevel.ts","../src/report/Report.ts","../src/rules/RuleEvaluator.ts","../src/score/ScoreCalculator.ts","../src/utils/validation.ts","../src/signals/SignalStore.ts","../src/engine/RiskEngine.ts","../src/rules/RuleBuilder.ts","../src/plugins/PluginRegistry.ts","../src/engine/Guardian.ts"],"names":["randomUUID"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { RiskLevelThreshold } from '../types/report.js';\n\n/** Default risk level thresholds used when none are configured. */\nexport const DEFAULT_RISK_LEVELS: readonly RiskLevelThreshold[] = [\n { max: 20, level: 'LOW' },\n { max: 40, level: 'MEDIUM' },\n { max: 60, level: 'HIGH' },\n { max: Infinity, level: 'CRITICAL' },\n] as const;\n","import type { RiskLevelThreshold } from '../types/report.js';\nimport { DEFAULT_RISK_LEVELS } from '../constants/defaults.js';\n\n/**\n * Resolves a risk level label from a score using configured thresholds.\n * Thresholds are evaluated in order; the first threshold where score <= max wins.\n */\nexport function resolveLevel(\n score: number,\n thresholds: readonly RiskLevelThreshold[] = DEFAULT_RISK_LEVELS,\n): string {\n for (const threshold of thresholds) {\n if (score <= threshold.max) {\n return threshold.level;\n }\n }\n\n const last = thresholds[thresholds.length - 1];\n return last?.level ?? 'UNKNOWN';\n}\n","import type { MatchedRule } from '../types/rules.js';\nimport type { RiskReport } from '../types/report.js';\nimport { resolveLevel } from '../utils/resolveLevel.js';\nimport type { RiskLevelThreshold } from '../types/report.js';\n\n/**\n * Builds immutable risk reports.\n */\nexport class ReportBuilder {\n /**\n * Build a frozen risk report from evaluation results.\n */\n build(\n score: number,\n matchedRules: readonly MatchedRule[],\n thresholds: readonly RiskLevelThreshold[],\n analyzedAt: string = new Date().toISOString(),\n ): RiskReport {\n const reasons = matchedRules.map((rule) => rule.reason);\n const level = resolveLevel(score, thresholds);\n\n const report: RiskReport = {\n score,\n level,\n reasons: Object.freeze([...reasons]),\n matchedRules: Object.freeze([...matchedRules]),\n analyzedAt,\n };\n\n return Object.freeze(report);\n }\n}\n","import type { MatchedRule, Rule } from '../types/rules.js';\nimport type { SignalMap } from '../types/signals.js';\n\n/**\n * Evaluates rules against a signal map and returns matched rules.\n */\nexport class RuleEvaluator {\n /**\n * Evaluate all rules against the given signals.\n * Rules are evaluated in registration order (exhaustive, no short-circuit).\n */\n evaluate<TSignals extends SignalMap>(\n rules: readonly Rule<TSignals>[],\n signals: TSignals,\n ): MatchedRule[] {\n const matched: MatchedRule[] = [];\n\n for (const rule of rules) {\n if (rule.when(signals)) {\n matched.push({\n id: rule.id,\n name: rule.name,\n score: rule.score,\n reason: rule.reason ?? rule.name,\n });\n }\n }\n\n return matched;\n }\n}\n","import type { MatchedRule } from '../types/rules.js';\n\n/**\n * Calculates risk score from matched rules.\n */\nexport class ScoreCalculator {\n /**\n * Sum scores from all matched rules.\n */\n calculate(matchedRules: readonly MatchedRule[]): number {\n return matchedRules.reduce((total, rule) => total + rule.score, 0);\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport type { SignalValue } from '../types/signals.js';\n\n/**\n * Validates that a value is an allowed signal primitive.\n * Signals must be string, number, boolean, or null — not objects or arrays.\n */\nexport function validateSignalValue(value: unknown): value is SignalValue {\n if (value === null) {\n return true;\n }\n\n const type = typeof value;\n return type === 'string' || type === 'number' || type === 'boolean';\n}\n\n/**\n * Generates a unique identifier for rules.\n */\nexport function generateId(): string {\n return randomUUID();\n}\n","import type { SignalMap, SignalValue } from '../types/signals.js';\nimport { validateSignalValue } from '../utils/validation.js';\n\n/**\n * Stores and retrieves signal values for risk evaluation.\n */\nexport class SignalStore {\n private readonly signals = new Map<string, SignalValue>();\n\n /**\n * Set a signal value. Overwrites any existing value for the key.\n */\n set(key: string, value: SignalValue): this {\n if (!validateSignalValue(value)) {\n throw new TypeError(\n `Invalid signal value for \"${key}\": signals must be string, number, boolean, or null`,\n );\n }\n\n this.signals.set(key, value);\n return this;\n }\n\n /**\n * Get a signal value by key.\n */\n get(key: string): SignalValue | undefined {\n return this.signals.get(key);\n }\n\n /**\n * Check if a signal exists.\n */\n has(key: string): boolean {\n return this.signals.has(key);\n }\n\n /**\n * Returns a frozen snapshot of all signals.\n */\n getAll(): SignalMap {\n const snapshot: Record<string, SignalValue> = {};\n for (const [key, value] of this.signals) {\n snapshot[key] = value;\n }\n return Object.freeze(snapshot);\n }\n\n /**\n * Clear all signals.\n */\n clear(): void {\n this.signals.clear();\n }\n}\n","import { DEFAULT_RISK_LEVELS } from '../constants/defaults.js';\nimport { ReportBuilder } from '../report/Report.js';\nimport { RuleEvaluator } from '../rules/RuleEvaluator.js';\nimport { ScoreCalculator } from '../score/ScoreCalculator.js';\nimport { SignalStore } from '../signals/SignalStore.js';\nimport type { Rule } from '../types/rules.js';\nimport type { RiskReport, RiskLevelThreshold } from '../types/report.js';\nimport type { SignalMap } from '../types/signals.js';\n\n/** Dependencies injected into RiskEngine. */\nexport interface RiskEngineDependencies {\n readonly signalStore: SignalStore;\n readonly ruleEvaluator: RuleEvaluator;\n readonly scoreCalculator: ScoreCalculator;\n readonly reportBuilder: ReportBuilder;\n}\n\n/**\n * Orchestrates signal evaluation, scoring, and report generation.\n */\nexport class RiskEngine {\n private readonly rules: Rule<SignalMap>[] = [];\n private readonly thresholds: readonly RiskLevelThreshold[];\n\n constructor(\n private readonly deps: RiskEngineDependencies,\n thresholds: readonly RiskLevelThreshold[] = DEFAULT_RISK_LEVELS,\n ) {\n this.thresholds = thresholds;\n }\n\n /**\n * Register a rule for evaluation.\n */\n addRule(rule: Rule<SignalMap>): void {\n this.rules.push(rule);\n }\n\n /**\n * Get all registered rules.\n */\n getRules(): readonly Rule<SignalMap>[] {\n return this.rules;\n }\n\n /**\n * Run the full risk analysis pipeline.\n */\n analyze(): RiskReport {\n const signals = this.deps.signalStore.getAll();\n const matchedRules = this.deps.ruleEvaluator.evaluate(this.rules, signals);\n const score = this.deps.scoreCalculator.calculate(matchedRules);\n\n return this.deps.reportBuilder.build(score, matchedRules, this.thresholds);\n }\n}\n","import type { CreateRuleInput, Rule } from '../types/rules.js';\nimport type { SignalMap } from '../types/signals.js';\nimport { generateId } from '../utils/validation.js';\n\n/**\n * Builds immutable rule definitions with auto-generated IDs.\n */\nexport class RuleBuilder {\n /**\n * Create a new rule from input configuration.\n */\n static create<TSignals extends SignalMap = SignalMap>(\n input: CreateRuleInput<TSignals>,\n ): Rule<TSignals> {\n const rule: Rule<TSignals> = {\n id: generateId(),\n name: input.name,\n score: input.score,\n when: input.when,\n ...(input.description !== undefined ? { description: input.description } : {}),\n ...(input.reason !== undefined ? { reason: input.reason } : {}),\n };\n\n return rule;\n }\n}\n","import type { Guardian } from '../engine/Guardian.js';\nimport type { Plugin } from './Plugin.js';\n\n/**\n * Thrown when attempting to register a plugin that is already installed.\n */\nexport class PluginAlreadyInstalledError extends Error {\n constructor(pluginName: string) {\n super(`Plugin \"${pluginName}\" is already installed`);\n this.name = 'PluginAlreadyInstalledError';\n }\n}\n\n/**\n * Manages plugin lifecycle: register once, track installed plugins.\n */\nexport class PluginRegistry {\n private readonly installed = new Set<string>();\n\n /**\n * Install a plugin on the given Guardian instance.\n * Each plugin name may only be installed once per Guardian instance.\n */\n install(plugin: Plugin, guardian: Guardian): void {\n if (this.installed.has(plugin.name)) {\n throw new PluginAlreadyInstalledError(plugin.name);\n }\n\n plugin.install(guardian);\n this.installed.add(plugin.name);\n }\n\n /**\n * Check if a plugin is installed by name.\n */\n has(name: string): boolean {\n return this.installed.has(name);\n }\n\n /**\n * Get names of all installed plugins in registration order.\n */\n getInstalled(): readonly string[] {\n return [...this.installed];\n }\n}\n","import { DEFAULT_RISK_LEVELS } from '../constants/defaults.js';\nimport { RiskEngine, type RiskEngineDependencies } from './RiskEngine.js';\nimport { ReportBuilder } from '../report/Report.js';\nimport { RuleBuilder } from '../rules/RuleBuilder.js';\nimport { RuleEvaluator } from '../rules/RuleEvaluator.js';\nimport { ScoreCalculator } from '../score/ScoreCalculator.js';\nimport { SignalStore } from '../signals/SignalStore.js';\nimport { PluginRegistry } from '../plugins/PluginRegistry.js';\nimport type { Plugin } from '../plugins/Plugin.js';\nimport type { CreateRuleInput } from '../types/rules.js';\nimport type { GuardianConfig, RiskReport } from '../types/report.js';\nimport type { SignalValue } from '../types/signals.js';\n\n/**\n * Fluent public API for risk analysis.\n * Collects signals and rules, then produces an immutable report.\n */\nexport class Guardian {\n private readonly signalStore: SignalStore;\n private readonly riskEngine: RiskEngine;\n private readonly pluginRegistry = new PluginRegistry();\n\n constructor(config: GuardianConfig = {}) {\n const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;\n this.signalStore = new SignalStore();\n\n const deps: RiskEngineDependencies = {\n signalStore: this.signalStore,\n ruleEvaluator: new RuleEvaluator(),\n scoreCalculator: new ScoreCalculator(),\n reportBuilder: new ReportBuilder(),\n };\n\n this.riskEngine = new RiskEngine(deps, thresholds);\n }\n\n /**\n * Add a signal value for risk evaluation.\n */\n signal(key: string, value: SignalValue): this {\n this.signalStore.set(key, value);\n return this;\n }\n\n /**\n * Register a rule. ID is auto-generated.\n */\n rule(input: CreateRuleInput): this {\n const rule = RuleBuilder.create(input);\n this.riskEngine.addRule(rule);\n return this;\n }\n\n /**\n * Install a plugin. Each plugin name may only be registered once.\n */\n use(plugin: Plugin): this {\n this.pluginRegistry.install(plugin, this);\n return this;\n }\n\n /**\n * Returns names of installed plugins.\n */\n getInstalledPlugins(): readonly string[] {\n return this.pluginRegistry.getInstalled();\n }\n\n /**\n * Run risk analysis and return an immutable report.\n */\n analyze(): RiskReport {\n return this.riskEngine.analyze();\n }\n\n /**\n * Clear all signals. Rules and installed plugins persist across resets.\n */\n reset(): this {\n this.signalStore.clear();\n return this;\n }\n}\n"]}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/constants/defaults.ts","../src/utils/resolveLevel.ts","../src/report/Report.ts","../src/rules/RuleEvaluator.ts","../src/score/ScoreCalculator.ts","../src/utils/validation.ts","../src/signals/SignalStore.ts","../src/engine/RiskEngine.ts","../src/rules/RuleBuilder.ts","../src/plugins/PluginRegistry.ts","../src/engine/Guardian.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { RiskLevelThreshold } from '../types/report.js';\n\n/** Default risk level thresholds used when none are configured. */\nexport const DEFAULT_RISK_LEVELS: readonly RiskLevelThreshold[] = [\n { max: 20, level: 'LOW' },\n { max: 40, level: 'MEDIUM' },\n { max: 60, level: 'HIGH' },\n { max: Infinity, level: 'CRITICAL' },\n] as const;\n","import type { RiskLevelThreshold } from '../types/report.js';\nimport { DEFAULT_RISK_LEVELS } from '../constants/defaults.js';\n\n/**\n * Resolves a risk level label from a score using configured thresholds.\n * Thresholds are evaluated in order; the first threshold where score <= max wins.\n */\nexport function resolveLevel(\n score: number,\n thresholds: readonly RiskLevelThreshold[] = DEFAULT_RISK_LEVELS,\n): string {\n for (const threshold of thresholds) {\n if (score <= threshold.max) {\n return threshold.level;\n }\n }\n\n const last = thresholds[thresholds.length - 1];\n return last?.level ?? 'UNKNOWN';\n}\n","import type { MatchedRule } from '../types/rules.js';\nimport type { RiskReport } from '../types/report.js';\nimport { resolveLevel } from '../utils/resolveLevel.js';\nimport type { RiskLevelThreshold } from '../types/report.js';\n\n/**\n * Builds immutable risk reports.\n */\nexport class ReportBuilder {\n /**\n * Build a frozen risk report from evaluation results.\n */\n build(\n score: number,\n matchedRules: readonly MatchedRule[],\n thresholds: readonly RiskLevelThreshold[],\n analyzedAt: string = new Date().toISOString(),\n ): RiskReport {\n const reasons = matchedRules.map((rule) => rule.reason);\n const level = resolveLevel(score, thresholds);\n\n const report: RiskReport = {\n score,\n level,\n reasons: Object.freeze([...reasons]),\n matchedRules: Object.freeze([...matchedRules]),\n analyzedAt,\n };\n\n return Object.freeze(report);\n }\n}\n","import type { MatchedRule, Rule } from '../types/rules.js';\nimport type { SignalMap } from '../types/signals.js';\n\n/**\n * Evaluates rules against a signal map and returns matched rules.\n */\nexport class RuleEvaluator {\n /**\n * Evaluate all rules against the given signals.\n * Rules are evaluated in registration order (exhaustive, no short-circuit).\n */\n evaluate<TSignals extends SignalMap>(\n rules: readonly Rule<TSignals>[],\n signals: TSignals,\n ): MatchedRule[] {\n const matched: MatchedRule[] = [];\n\n for (const rule of rules) {\n if (rule.when(signals)) {\n matched.push({\n id: rule.id,\n name: rule.name,\n score: rule.score,\n reason: rule.reason ?? rule.name,\n });\n }\n }\n\n return matched;\n }\n}\n","import type { MatchedRule } from '../types/rules.js';\n\n/**\n * Calculates risk score from matched rules.\n */\nexport class ScoreCalculator {\n /**\n * Sum scores from all matched rules.\n */\n calculate(matchedRules: readonly MatchedRule[]): number {\n return matchedRules.reduce((total, rule) => total + rule.score, 0);\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport type { SignalValue } from '../types/signals.js';\n\n/**\n * Validates that a value is an allowed signal primitive.\n * Signals must be string, number, boolean, or null — not objects or arrays.\n */\nexport function validateSignalValue(value: unknown): value is SignalValue {\n if (value === null) {\n return true;\n }\n\n const type = typeof value;\n return type === 'string' || type === 'number' || type === 'boolean';\n}\n\n/**\n * Generates a unique identifier for rules.\n */\nexport function generateId(): string {\n return randomUUID();\n}\n","import type { SignalMap, SignalValue } from '../types/signals.js';\nimport { validateSignalValue } from '../utils/validation.js';\n\n/**\n * Stores and retrieves signal values for risk evaluation.\n */\nexport class SignalStore {\n private readonly signals = new Map<string, SignalValue>();\n\n /**\n * Set a signal value. Overwrites any existing value for the key.\n */\n set(key: string, value: SignalValue): this {\n if (!validateSignalValue(value)) {\n throw new TypeError(\n `Invalid signal value for \"${key}\": signals must be string, number, boolean, or null`,\n );\n }\n\n this.signals.set(key, value);\n return this;\n }\n\n /**\n * Get a signal value by key.\n */\n get(key: string): SignalValue | undefined {\n return this.signals.get(key);\n }\n\n /**\n * Check if a signal exists.\n */\n has(key: string): boolean {\n return this.signals.has(key);\n }\n\n /**\n * Returns a frozen snapshot of all signals.\n */\n getAll(): SignalMap {\n const snapshot: Record<string, SignalValue> = {};\n for (const [key, value] of this.signals) {\n snapshot[key] = value;\n }\n return Object.freeze(snapshot);\n }\n\n /**\n * Clear all signals.\n */\n clear(): void {\n this.signals.clear();\n }\n}\n","import { DEFAULT_RISK_LEVELS } from '../constants/defaults.js';\nimport { ReportBuilder } from '../report/Report.js';\nimport { RuleEvaluator } from '../rules/RuleEvaluator.js';\nimport { ScoreCalculator } from '../score/ScoreCalculator.js';\nimport { SignalStore } from '../signals/SignalStore.js';\nimport type { Rule } from '../types/rules.js';\nimport type { RiskReport, RiskLevelThreshold } from '../types/report.js';\nimport type { SignalMap } from '../types/signals.js';\n\n/** Dependencies injected into RiskEngine. */\nexport interface RiskEngineDependencies {\n readonly signalStore: SignalStore;\n readonly ruleEvaluator: RuleEvaluator;\n readonly scoreCalculator: ScoreCalculator;\n readonly reportBuilder: ReportBuilder;\n}\n\n/**\n * Orchestrates signal evaluation, scoring, and report generation.\n */\nexport class RiskEngine {\n private readonly rules: Rule<SignalMap>[] = [];\n private readonly thresholds: readonly RiskLevelThreshold[];\n\n constructor(\n private readonly deps: RiskEngineDependencies,\n thresholds: readonly RiskLevelThreshold[] = DEFAULT_RISK_LEVELS,\n ) {\n this.thresholds = thresholds;\n }\n\n /**\n * Register a rule for evaluation.\n */\n addRule(rule: Rule<SignalMap>): void {\n this.rules.push(rule);\n }\n\n /**\n * Get all registered rules.\n */\n getRules(): readonly Rule<SignalMap>[] {\n return this.rules;\n }\n\n /**\n * Run the full risk analysis pipeline.\n */\n analyze(): RiskReport {\n const signals = this.deps.signalStore.getAll();\n const matchedRules = this.deps.ruleEvaluator.evaluate(this.rules, signals);\n const score = this.deps.scoreCalculator.calculate(matchedRules);\n\n return this.deps.reportBuilder.build(score, matchedRules, this.thresholds);\n }\n}\n","import type { CreateRuleInput, Rule } from '../types/rules.js';\nimport type { SignalMap } from '../types/signals.js';\nimport { generateId } from '../utils/validation.js';\n\n/**\n * Builds immutable rule definitions with auto-generated IDs.\n */\nexport class RuleBuilder {\n /**\n * Create a new rule from input configuration.\n */\n static create<TSignals extends SignalMap = SignalMap>(\n input: CreateRuleInput<TSignals>,\n ): Rule<TSignals> {\n const rule: Rule<TSignals> = {\n id: generateId(),\n name: input.name,\n score: input.score,\n when: input.when,\n ...(input.description !== undefined ? { description: input.description } : {}),\n ...(input.reason !== undefined ? { reason: input.reason } : {}),\n };\n\n return rule;\n }\n}\n","import type { Guardian } from '../engine/Guardian.js';\nimport type { Plugin } from './Plugin.js';\n\n/**\n * Thrown when attempting to register a plugin that is already installed.\n */\nexport class PluginAlreadyInstalledError extends Error {\n constructor(pluginName: string) {\n super(`Plugin \"${pluginName}\" is already installed`);\n this.name = 'PluginAlreadyInstalledError';\n }\n}\n\n/**\n * Manages plugin lifecycle: register once, track installed plugins.\n */\nexport class PluginRegistry {\n private readonly installed = new Set<string>();\n\n /**\n * Install a plugin on the given Guardian instance.\n * Each plugin name may only be installed once per Guardian instance.\n */\n install(plugin: Plugin, guardian: Guardian): void {\n if (this.installed.has(plugin.name)) {\n throw new PluginAlreadyInstalledError(plugin.name);\n }\n\n plugin.install(guardian);\n this.installed.add(plugin.name);\n }\n\n /**\n * Check if a plugin is installed by name.\n */\n has(name: string): boolean {\n return this.installed.has(name);\n }\n\n /**\n * Get names of all installed plugins in registration order.\n */\n getInstalled(): readonly string[] {\n return [...this.installed];\n }\n}\n","import { DEFAULT_RISK_LEVELS } from '../constants/defaults.js';\nimport { RiskEngine, type RiskEngineDependencies } from './RiskEngine.js';\nimport { ReportBuilder } from '../report/Report.js';\nimport { RuleBuilder } from '../rules/RuleBuilder.js';\nimport { RuleEvaluator } from '../rules/RuleEvaluator.js';\nimport { ScoreCalculator } from '../score/ScoreCalculator.js';\nimport { SignalStore } from '../signals/SignalStore.js';\nimport { PluginRegistry } from '../plugins/PluginRegistry.js';\nimport type { Plugin } from '../plugins/Plugin.js';\nimport type { CreateRuleInput } from '../types/rules.js';\nimport type { GuardianConfig, RiskReport } from '../types/report.js';\nimport type { SignalValue } from '../types/signals.js';\n\n/**\n * Fluent public API for risk analysis.\n * Collects signals and rules, then produces an immutable report.\n */\nexport class Guardian {\n private readonly signalStore: SignalStore;\n private readonly riskEngine: RiskEngine;\n private readonly pluginRegistry = new PluginRegistry();\n\n constructor(config: GuardianConfig = {}) {\n const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;\n this.signalStore = new SignalStore();\n\n const deps: RiskEngineDependencies = {\n signalStore: this.signalStore,\n ruleEvaluator: new RuleEvaluator(),\n scoreCalculator: new ScoreCalculator(),\n reportBuilder: new ReportBuilder(),\n };\n\n this.riskEngine = new RiskEngine(deps, thresholds);\n }\n\n /**\n * Add a signal value for risk evaluation.\n */\n signal(key: string, value: SignalValue): this {\n this.signalStore.set(key, value);\n return this;\n }\n\n /**\n * Register a rule. ID is auto-generated.\n */\n rule(input: CreateRuleInput): this {\n const rule = RuleBuilder.create(input);\n this.riskEngine.addRule(rule);\n return this;\n }\n\n /**\n * Install a plugin. Each plugin name may only be registered once.\n */\n use(plugin: Plugin): this {\n this.pluginRegistry.install(plugin, this);\n return this;\n }\n\n /**\n * Returns names of installed plugins.\n */\n getInstalledPlugins(): readonly string[] {\n return this.pluginRegistry.getInstalled();\n }\n\n /**\n * Run risk analysis and return an immutable report.\n */\n analyze(): RiskReport {\n return this.riskEngine.analyze();\n }\n\n /**\n * Clear all signals. Rules and installed plugins persist across resets.\n */\n reset(): this {\n this.signalStore.clear();\n return this;\n }\n}\n"]}