guardian-risk 0.1.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.
- package/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/index.cjs +234 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +186 -0
- package/dist/index.d.ts +186 -0
- package/dist/index.js +225 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Guardian Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# guardian-risk
|
|
2
|
+
|
|
3
|
+
Configurable risk decision engine for TypeScript. Evaluate signals against rules and get an explainable risk score.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install guardian-risk
|
|
9
|
+
# or
|
|
10
|
+
pnpm add guardian-risk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Guardian } from 'guardian-risk';
|
|
17
|
+
|
|
18
|
+
const guardian = new Guardian();
|
|
19
|
+
|
|
20
|
+
const report = guardian
|
|
21
|
+
.signal('postsPerMinute', 50)
|
|
22
|
+
.signal('emailVerified', false)
|
|
23
|
+
.rule({
|
|
24
|
+
name: 'HighPosting',
|
|
25
|
+
when: (s) => (s.postsPerMinute as number) > 20,
|
|
26
|
+
score: 20,
|
|
27
|
+
})
|
|
28
|
+
.rule({
|
|
29
|
+
name: 'UnverifiedEmail',
|
|
30
|
+
when: (s) => s.emailVerified === false,
|
|
31
|
+
score: 15,
|
|
32
|
+
})
|
|
33
|
+
.analyze();
|
|
34
|
+
|
|
35
|
+
console.log(report.score); // 35
|
|
36
|
+
console.log(report.level); // MEDIUM
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## API
|
|
40
|
+
|
|
41
|
+
- `guardian.signal(key, value)` — add a signal
|
|
42
|
+
- `guardian.rule({ name, when, score, reason? })` — register a rule
|
|
43
|
+
- `guardian.analyze()` — run evaluation, returns `RiskReport`
|
|
44
|
+
- `guardian.reset()` — clear signals (rules persist)
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
// src/constants/defaults.ts
|
|
6
|
+
var DEFAULT_RISK_LEVELS = [
|
|
7
|
+
{ max: 20, level: "LOW" },
|
|
8
|
+
{ max: 40, level: "MEDIUM" },
|
|
9
|
+
{ max: 60, level: "HIGH" },
|
|
10
|
+
{ max: Infinity, level: "CRITICAL" }
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
// src/utils/resolveLevel.ts
|
|
14
|
+
function resolveLevel(score, thresholds = DEFAULT_RISK_LEVELS) {
|
|
15
|
+
for (const threshold of thresholds) {
|
|
16
|
+
if (score <= threshold.max) {
|
|
17
|
+
return threshold.level;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const last = thresholds[thresholds.length - 1];
|
|
21
|
+
return last?.level ?? "UNKNOWN";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/report/Report.ts
|
|
25
|
+
var ReportBuilder = class {
|
|
26
|
+
/**
|
|
27
|
+
* Build a frozen risk report from evaluation results.
|
|
28
|
+
*/
|
|
29
|
+
build(score, matchedRules, thresholds, analyzedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
30
|
+
const reasons = matchedRules.map((rule) => rule.reason);
|
|
31
|
+
const level = resolveLevel(score, thresholds);
|
|
32
|
+
const report = {
|
|
33
|
+
score,
|
|
34
|
+
level,
|
|
35
|
+
reasons: Object.freeze([...reasons]),
|
|
36
|
+
matchedRules: Object.freeze([...matchedRules]),
|
|
37
|
+
analyzedAt
|
|
38
|
+
};
|
|
39
|
+
return Object.freeze(report);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/rules/RuleEvaluator.ts
|
|
44
|
+
var RuleEvaluator = class {
|
|
45
|
+
/**
|
|
46
|
+
* Evaluate all rules against the given signals.
|
|
47
|
+
* Rules are evaluated in registration order (exhaustive, no short-circuit).
|
|
48
|
+
*/
|
|
49
|
+
evaluate(rules, signals) {
|
|
50
|
+
const matched = [];
|
|
51
|
+
for (const rule of rules) {
|
|
52
|
+
if (rule.when(signals)) {
|
|
53
|
+
matched.push({
|
|
54
|
+
id: rule.id,
|
|
55
|
+
name: rule.name,
|
|
56
|
+
score: rule.score,
|
|
57
|
+
reason: rule.reason ?? rule.name
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return matched;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// src/score/ScoreCalculator.ts
|
|
66
|
+
var ScoreCalculator = class {
|
|
67
|
+
/**
|
|
68
|
+
* Sum scores from all matched rules.
|
|
69
|
+
*/
|
|
70
|
+
calculate(matchedRules) {
|
|
71
|
+
return matchedRules.reduce((total, rule) => total + rule.score, 0);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
function validateSignalValue(value) {
|
|
75
|
+
if (value === null) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
const type = typeof value;
|
|
79
|
+
return type === "string" || type === "number" || type === "boolean";
|
|
80
|
+
}
|
|
81
|
+
function generateId() {
|
|
82
|
+
return crypto.randomUUID();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/signals/SignalStore.ts
|
|
86
|
+
var SignalStore = class {
|
|
87
|
+
signals = /* @__PURE__ */ new Map();
|
|
88
|
+
/**
|
|
89
|
+
* Set a signal value. Overwrites any existing value for the key.
|
|
90
|
+
*/
|
|
91
|
+
set(key, value) {
|
|
92
|
+
if (!validateSignalValue(value)) {
|
|
93
|
+
throw new TypeError(
|
|
94
|
+
`Invalid signal value for "${key}": signals must be string, number, boolean, or null`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
this.signals.set(key, value);
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get a signal value by key.
|
|
102
|
+
*/
|
|
103
|
+
get(key) {
|
|
104
|
+
return this.signals.get(key);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check if a signal exists.
|
|
108
|
+
*/
|
|
109
|
+
has(key) {
|
|
110
|
+
return this.signals.has(key);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Returns a frozen snapshot of all signals.
|
|
114
|
+
*/
|
|
115
|
+
getAll() {
|
|
116
|
+
const snapshot = {};
|
|
117
|
+
for (const [key, value] of this.signals) {
|
|
118
|
+
snapshot[key] = value;
|
|
119
|
+
}
|
|
120
|
+
return Object.freeze(snapshot);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Clear all signals.
|
|
124
|
+
*/
|
|
125
|
+
clear() {
|
|
126
|
+
this.signals.clear();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// src/engine/RiskEngine.ts
|
|
131
|
+
var RiskEngine = class {
|
|
132
|
+
constructor(deps, thresholds = DEFAULT_RISK_LEVELS) {
|
|
133
|
+
this.deps = deps;
|
|
134
|
+
this.thresholds = thresholds;
|
|
135
|
+
}
|
|
136
|
+
deps;
|
|
137
|
+
rules = [];
|
|
138
|
+
thresholds;
|
|
139
|
+
/**
|
|
140
|
+
* Register a rule for evaluation.
|
|
141
|
+
*/
|
|
142
|
+
addRule(rule) {
|
|
143
|
+
this.rules.push(rule);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get all registered rules.
|
|
147
|
+
*/
|
|
148
|
+
getRules() {
|
|
149
|
+
return this.rules;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Run the full risk analysis pipeline.
|
|
153
|
+
*/
|
|
154
|
+
analyze() {
|
|
155
|
+
const signals = this.deps.signalStore.getAll();
|
|
156
|
+
const matchedRules = this.deps.ruleEvaluator.evaluate(this.rules, signals);
|
|
157
|
+
const score = this.deps.scoreCalculator.calculate(matchedRules);
|
|
158
|
+
return this.deps.reportBuilder.build(score, matchedRules, this.thresholds);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// src/rules/RuleBuilder.ts
|
|
163
|
+
var RuleBuilder = class {
|
|
164
|
+
/**
|
|
165
|
+
* Create a new rule from input configuration.
|
|
166
|
+
*/
|
|
167
|
+
static create(input) {
|
|
168
|
+
const rule = {
|
|
169
|
+
id: generateId(),
|
|
170
|
+
name: input.name,
|
|
171
|
+
score: input.score,
|
|
172
|
+
when: input.when,
|
|
173
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
174
|
+
...input.reason !== void 0 ? { reason: input.reason } : {}
|
|
175
|
+
};
|
|
176
|
+
return rule;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// src/engine/Guardian.ts
|
|
181
|
+
var Guardian = class {
|
|
182
|
+
signalStore;
|
|
183
|
+
riskEngine;
|
|
184
|
+
constructor(config = {}) {
|
|
185
|
+
const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;
|
|
186
|
+
this.signalStore = new SignalStore();
|
|
187
|
+
const deps = {
|
|
188
|
+
signalStore: this.signalStore,
|
|
189
|
+
ruleEvaluator: new RuleEvaluator(),
|
|
190
|
+
scoreCalculator: new ScoreCalculator(),
|
|
191
|
+
reportBuilder: new ReportBuilder()
|
|
192
|
+
};
|
|
193
|
+
this.riskEngine = new RiskEngine(deps, thresholds);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Add a signal value for risk evaluation.
|
|
197
|
+
*/
|
|
198
|
+
signal(key, value) {
|
|
199
|
+
this.signalStore.set(key, value);
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Register a rule. ID is auto-generated.
|
|
204
|
+
*/
|
|
205
|
+
rule(input) {
|
|
206
|
+
const rule = RuleBuilder.create(input);
|
|
207
|
+
this.riskEngine.addRule(rule);
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Run risk analysis and return an immutable report.
|
|
212
|
+
*/
|
|
213
|
+
analyze() {
|
|
214
|
+
return this.riskEngine.analyze();
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Clear all signals. Rules persist across resets.
|
|
218
|
+
*/
|
|
219
|
+
reset() {
|
|
220
|
+
this.signalStore.clear();
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
exports.DEFAULT_RISK_LEVELS = DEFAULT_RISK_LEVELS;
|
|
226
|
+
exports.Guardian = Guardian;
|
|
227
|
+
exports.RiskEngine = RiskEngine;
|
|
228
|
+
exports.RuleBuilder = RuleBuilder;
|
|
229
|
+
exports.RuleEvaluator = RuleEvaluator;
|
|
230
|
+
exports.ScoreCalculator = ScoreCalculator;
|
|
231
|
+
exports.SignalStore = SignalStore;
|
|
232
|
+
exports.resolveLevel = resolveLevel;
|
|
233
|
+
//# sourceMappingURL=index.cjs.map
|
|
234
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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/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 { 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 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\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 * 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 persist across resets.\n */\n reset(): this {\n this.signalStore.clear();\n return this;\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/** Primitive signal value types supported by Guardian. */
|
|
2
|
+
type SignalValue = string | number | boolean | null;
|
|
3
|
+
/** Read-only map of signal key to value. */
|
|
4
|
+
type SignalMap = Readonly<Record<string, SignalValue>>;
|
|
5
|
+
/** Definition of a single signal. */
|
|
6
|
+
interface SignalDefinition<T extends SignalValue = SignalValue> {
|
|
7
|
+
readonly key: string;
|
|
8
|
+
readonly value: T;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** A rule that evaluates signals and contributes risk when matched. */
|
|
12
|
+
interface Rule<TSignals extends SignalMap = SignalMap> {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly description?: string;
|
|
16
|
+
readonly score: number;
|
|
17
|
+
readonly when: (signals: TSignals) => boolean;
|
|
18
|
+
readonly reason?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Input for creating a new rule (id is auto-generated). */
|
|
21
|
+
interface CreateRuleInput<TSignals extends SignalMap = SignalMap> {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly description?: string;
|
|
24
|
+
readonly score: number;
|
|
25
|
+
readonly when: (signals: TSignals) => boolean;
|
|
26
|
+
readonly reason?: string;
|
|
27
|
+
}
|
|
28
|
+
/** A rule that matched during evaluation. */
|
|
29
|
+
interface MatchedRule {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly score: number;
|
|
33
|
+
readonly reason: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Immutable risk analysis report. */
|
|
37
|
+
interface RiskReport {
|
|
38
|
+
readonly score: number;
|
|
39
|
+
readonly level: string;
|
|
40
|
+
readonly reasons: readonly string[];
|
|
41
|
+
readonly matchedRules: readonly MatchedRule[];
|
|
42
|
+
readonly analyzedAt: string;
|
|
43
|
+
}
|
|
44
|
+
/** Threshold mapping a maximum score to a risk level label. */
|
|
45
|
+
interface RiskLevelThreshold {
|
|
46
|
+
readonly max: number;
|
|
47
|
+
readonly level: string;
|
|
48
|
+
}
|
|
49
|
+
/** Configuration options for Guardian. */
|
|
50
|
+
interface GuardianConfig {
|
|
51
|
+
readonly levels?: readonly RiskLevelThreshold[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fluent public API for risk analysis.
|
|
56
|
+
* Collects signals and rules, then produces an immutable report.
|
|
57
|
+
*/
|
|
58
|
+
declare class Guardian {
|
|
59
|
+
private readonly signalStore;
|
|
60
|
+
private readonly riskEngine;
|
|
61
|
+
constructor(config?: GuardianConfig);
|
|
62
|
+
/**
|
|
63
|
+
* Add a signal value for risk evaluation.
|
|
64
|
+
*/
|
|
65
|
+
signal(key: string, value: SignalValue): this;
|
|
66
|
+
/**
|
|
67
|
+
* Register a rule. ID is auto-generated.
|
|
68
|
+
*/
|
|
69
|
+
rule(input: CreateRuleInput): this;
|
|
70
|
+
/**
|
|
71
|
+
* Run risk analysis and return an immutable report.
|
|
72
|
+
*/
|
|
73
|
+
analyze(): RiskReport;
|
|
74
|
+
/**
|
|
75
|
+
* Clear all signals. Rules persist across resets.
|
|
76
|
+
*/
|
|
77
|
+
reset(): this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Builds immutable risk reports.
|
|
82
|
+
*/
|
|
83
|
+
declare class ReportBuilder {
|
|
84
|
+
/**
|
|
85
|
+
* Build a frozen risk report from evaluation results.
|
|
86
|
+
*/
|
|
87
|
+
build(score: number, matchedRules: readonly MatchedRule[], thresholds: readonly RiskLevelThreshold[], analyzedAt?: string): RiskReport;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Evaluates rules against a signal map and returns matched rules.
|
|
92
|
+
*/
|
|
93
|
+
declare class RuleEvaluator {
|
|
94
|
+
/**
|
|
95
|
+
* Evaluate all rules against the given signals.
|
|
96
|
+
* Rules are evaluated in registration order (exhaustive, no short-circuit).
|
|
97
|
+
*/
|
|
98
|
+
evaluate<TSignals extends SignalMap>(rules: readonly Rule<TSignals>[], signals: TSignals): MatchedRule[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Calculates risk score from matched rules.
|
|
103
|
+
*/
|
|
104
|
+
declare class ScoreCalculator {
|
|
105
|
+
/**
|
|
106
|
+
* Sum scores from all matched rules.
|
|
107
|
+
*/
|
|
108
|
+
calculate(matchedRules: readonly MatchedRule[]): number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Stores and retrieves signal values for risk evaluation.
|
|
113
|
+
*/
|
|
114
|
+
declare class SignalStore {
|
|
115
|
+
private readonly signals;
|
|
116
|
+
/**
|
|
117
|
+
* Set a signal value. Overwrites any existing value for the key.
|
|
118
|
+
*/
|
|
119
|
+
set(key: string, value: SignalValue): this;
|
|
120
|
+
/**
|
|
121
|
+
* Get a signal value by key.
|
|
122
|
+
*/
|
|
123
|
+
get(key: string): SignalValue | undefined;
|
|
124
|
+
/**
|
|
125
|
+
* Check if a signal exists.
|
|
126
|
+
*/
|
|
127
|
+
has(key: string): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Returns a frozen snapshot of all signals.
|
|
130
|
+
*/
|
|
131
|
+
getAll(): SignalMap;
|
|
132
|
+
/**
|
|
133
|
+
* Clear all signals.
|
|
134
|
+
*/
|
|
135
|
+
clear(): void;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Dependencies injected into RiskEngine. */
|
|
139
|
+
interface RiskEngineDependencies {
|
|
140
|
+
readonly signalStore: SignalStore;
|
|
141
|
+
readonly ruleEvaluator: RuleEvaluator;
|
|
142
|
+
readonly scoreCalculator: ScoreCalculator;
|
|
143
|
+
readonly reportBuilder: ReportBuilder;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Orchestrates signal evaluation, scoring, and report generation.
|
|
147
|
+
*/
|
|
148
|
+
declare class RiskEngine {
|
|
149
|
+
private readonly deps;
|
|
150
|
+
private readonly rules;
|
|
151
|
+
private readonly thresholds;
|
|
152
|
+
constructor(deps: RiskEngineDependencies, thresholds?: readonly RiskLevelThreshold[]);
|
|
153
|
+
/**
|
|
154
|
+
* Register a rule for evaluation.
|
|
155
|
+
*/
|
|
156
|
+
addRule(rule: Rule<SignalMap>): void;
|
|
157
|
+
/**
|
|
158
|
+
* Get all registered rules.
|
|
159
|
+
*/
|
|
160
|
+
getRules(): readonly Rule<SignalMap>[];
|
|
161
|
+
/**
|
|
162
|
+
* Run the full risk analysis pipeline.
|
|
163
|
+
*/
|
|
164
|
+
analyze(): RiskReport;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Builds immutable rule definitions with auto-generated IDs.
|
|
169
|
+
*/
|
|
170
|
+
declare class RuleBuilder {
|
|
171
|
+
/**
|
|
172
|
+
* Create a new rule from input configuration.
|
|
173
|
+
*/
|
|
174
|
+
static create<TSignals extends SignalMap = SignalMap>(input: CreateRuleInput<TSignals>): Rule<TSignals>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Resolves a risk level label from a score using configured thresholds.
|
|
179
|
+
* Thresholds are evaluated in order; the first threshold where score <= max wins.
|
|
180
|
+
*/
|
|
181
|
+
declare function resolveLevel(score: number, thresholds?: readonly RiskLevelThreshold[]): string;
|
|
182
|
+
|
|
183
|
+
/** Default risk level thresholds used when none are configured. */
|
|
184
|
+
declare const DEFAULT_RISK_LEVELS: readonly RiskLevelThreshold[];
|
|
185
|
+
|
|
186
|
+
export { type CreateRuleInput, DEFAULT_RISK_LEVELS, Guardian, type GuardianConfig, type MatchedRule, 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
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/** Primitive signal value types supported by Guardian. */
|
|
2
|
+
type SignalValue = string | number | boolean | null;
|
|
3
|
+
/** Read-only map of signal key to value. */
|
|
4
|
+
type SignalMap = Readonly<Record<string, SignalValue>>;
|
|
5
|
+
/** Definition of a single signal. */
|
|
6
|
+
interface SignalDefinition<T extends SignalValue = SignalValue> {
|
|
7
|
+
readonly key: string;
|
|
8
|
+
readonly value: T;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** A rule that evaluates signals and contributes risk when matched. */
|
|
12
|
+
interface Rule<TSignals extends SignalMap = SignalMap> {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly description?: string;
|
|
16
|
+
readonly score: number;
|
|
17
|
+
readonly when: (signals: TSignals) => boolean;
|
|
18
|
+
readonly reason?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Input for creating a new rule (id is auto-generated). */
|
|
21
|
+
interface CreateRuleInput<TSignals extends SignalMap = SignalMap> {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly description?: string;
|
|
24
|
+
readonly score: number;
|
|
25
|
+
readonly when: (signals: TSignals) => boolean;
|
|
26
|
+
readonly reason?: string;
|
|
27
|
+
}
|
|
28
|
+
/** A rule that matched during evaluation. */
|
|
29
|
+
interface MatchedRule {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly score: number;
|
|
33
|
+
readonly reason: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Immutable risk analysis report. */
|
|
37
|
+
interface RiskReport {
|
|
38
|
+
readonly score: number;
|
|
39
|
+
readonly level: string;
|
|
40
|
+
readonly reasons: readonly string[];
|
|
41
|
+
readonly matchedRules: readonly MatchedRule[];
|
|
42
|
+
readonly analyzedAt: string;
|
|
43
|
+
}
|
|
44
|
+
/** Threshold mapping a maximum score to a risk level label. */
|
|
45
|
+
interface RiskLevelThreshold {
|
|
46
|
+
readonly max: number;
|
|
47
|
+
readonly level: string;
|
|
48
|
+
}
|
|
49
|
+
/** Configuration options for Guardian. */
|
|
50
|
+
interface GuardianConfig {
|
|
51
|
+
readonly levels?: readonly RiskLevelThreshold[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fluent public API for risk analysis.
|
|
56
|
+
* Collects signals and rules, then produces an immutable report.
|
|
57
|
+
*/
|
|
58
|
+
declare class Guardian {
|
|
59
|
+
private readonly signalStore;
|
|
60
|
+
private readonly riskEngine;
|
|
61
|
+
constructor(config?: GuardianConfig);
|
|
62
|
+
/**
|
|
63
|
+
* Add a signal value for risk evaluation.
|
|
64
|
+
*/
|
|
65
|
+
signal(key: string, value: SignalValue): this;
|
|
66
|
+
/**
|
|
67
|
+
* Register a rule. ID is auto-generated.
|
|
68
|
+
*/
|
|
69
|
+
rule(input: CreateRuleInput): this;
|
|
70
|
+
/**
|
|
71
|
+
* Run risk analysis and return an immutable report.
|
|
72
|
+
*/
|
|
73
|
+
analyze(): RiskReport;
|
|
74
|
+
/**
|
|
75
|
+
* Clear all signals. Rules persist across resets.
|
|
76
|
+
*/
|
|
77
|
+
reset(): this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Builds immutable risk reports.
|
|
82
|
+
*/
|
|
83
|
+
declare class ReportBuilder {
|
|
84
|
+
/**
|
|
85
|
+
* Build a frozen risk report from evaluation results.
|
|
86
|
+
*/
|
|
87
|
+
build(score: number, matchedRules: readonly MatchedRule[], thresholds: readonly RiskLevelThreshold[], analyzedAt?: string): RiskReport;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Evaluates rules against a signal map and returns matched rules.
|
|
92
|
+
*/
|
|
93
|
+
declare class RuleEvaluator {
|
|
94
|
+
/**
|
|
95
|
+
* Evaluate all rules against the given signals.
|
|
96
|
+
* Rules are evaluated in registration order (exhaustive, no short-circuit).
|
|
97
|
+
*/
|
|
98
|
+
evaluate<TSignals extends SignalMap>(rules: readonly Rule<TSignals>[], signals: TSignals): MatchedRule[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Calculates risk score from matched rules.
|
|
103
|
+
*/
|
|
104
|
+
declare class ScoreCalculator {
|
|
105
|
+
/**
|
|
106
|
+
* Sum scores from all matched rules.
|
|
107
|
+
*/
|
|
108
|
+
calculate(matchedRules: readonly MatchedRule[]): number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Stores and retrieves signal values for risk evaluation.
|
|
113
|
+
*/
|
|
114
|
+
declare class SignalStore {
|
|
115
|
+
private readonly signals;
|
|
116
|
+
/**
|
|
117
|
+
* Set a signal value. Overwrites any existing value for the key.
|
|
118
|
+
*/
|
|
119
|
+
set(key: string, value: SignalValue): this;
|
|
120
|
+
/**
|
|
121
|
+
* Get a signal value by key.
|
|
122
|
+
*/
|
|
123
|
+
get(key: string): SignalValue | undefined;
|
|
124
|
+
/**
|
|
125
|
+
* Check if a signal exists.
|
|
126
|
+
*/
|
|
127
|
+
has(key: string): boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Returns a frozen snapshot of all signals.
|
|
130
|
+
*/
|
|
131
|
+
getAll(): SignalMap;
|
|
132
|
+
/**
|
|
133
|
+
* Clear all signals.
|
|
134
|
+
*/
|
|
135
|
+
clear(): void;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Dependencies injected into RiskEngine. */
|
|
139
|
+
interface RiskEngineDependencies {
|
|
140
|
+
readonly signalStore: SignalStore;
|
|
141
|
+
readonly ruleEvaluator: RuleEvaluator;
|
|
142
|
+
readonly scoreCalculator: ScoreCalculator;
|
|
143
|
+
readonly reportBuilder: ReportBuilder;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Orchestrates signal evaluation, scoring, and report generation.
|
|
147
|
+
*/
|
|
148
|
+
declare class RiskEngine {
|
|
149
|
+
private readonly deps;
|
|
150
|
+
private readonly rules;
|
|
151
|
+
private readonly thresholds;
|
|
152
|
+
constructor(deps: RiskEngineDependencies, thresholds?: readonly RiskLevelThreshold[]);
|
|
153
|
+
/**
|
|
154
|
+
* Register a rule for evaluation.
|
|
155
|
+
*/
|
|
156
|
+
addRule(rule: Rule<SignalMap>): void;
|
|
157
|
+
/**
|
|
158
|
+
* Get all registered rules.
|
|
159
|
+
*/
|
|
160
|
+
getRules(): readonly Rule<SignalMap>[];
|
|
161
|
+
/**
|
|
162
|
+
* Run the full risk analysis pipeline.
|
|
163
|
+
*/
|
|
164
|
+
analyze(): RiskReport;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Builds immutable rule definitions with auto-generated IDs.
|
|
169
|
+
*/
|
|
170
|
+
declare class RuleBuilder {
|
|
171
|
+
/**
|
|
172
|
+
* Create a new rule from input configuration.
|
|
173
|
+
*/
|
|
174
|
+
static create<TSignals extends SignalMap = SignalMap>(input: CreateRuleInput<TSignals>): Rule<TSignals>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Resolves a risk level label from a score using configured thresholds.
|
|
179
|
+
* Thresholds are evaluated in order; the first threshold where score <= max wins.
|
|
180
|
+
*/
|
|
181
|
+
declare function resolveLevel(score: number, thresholds?: readonly RiskLevelThreshold[]): string;
|
|
182
|
+
|
|
183
|
+
/** Default risk level thresholds used when none are configured. */
|
|
184
|
+
declare const DEFAULT_RISK_LEVELS: readonly RiskLevelThreshold[];
|
|
185
|
+
|
|
186
|
+
export { type CreateRuleInput, DEFAULT_RISK_LEVELS, Guardian, type GuardianConfig, type MatchedRule, RiskEngine, type RiskEngineDependencies, type RiskLevelThreshold, type RiskReport, type Rule, RuleBuilder, RuleEvaluator, ScoreCalculator, type SignalDefinition, type SignalMap, SignalStore, type SignalValue, resolveLevel };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
|
|
3
|
+
// src/constants/defaults.ts
|
|
4
|
+
var DEFAULT_RISK_LEVELS = [
|
|
5
|
+
{ max: 20, level: "LOW" },
|
|
6
|
+
{ max: 40, level: "MEDIUM" },
|
|
7
|
+
{ max: 60, level: "HIGH" },
|
|
8
|
+
{ max: Infinity, level: "CRITICAL" }
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
// src/utils/resolveLevel.ts
|
|
12
|
+
function resolveLevel(score, thresholds = DEFAULT_RISK_LEVELS) {
|
|
13
|
+
for (const threshold of thresholds) {
|
|
14
|
+
if (score <= threshold.max) {
|
|
15
|
+
return threshold.level;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const last = thresholds[thresholds.length - 1];
|
|
19
|
+
return last?.level ?? "UNKNOWN";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/report/Report.ts
|
|
23
|
+
var ReportBuilder = class {
|
|
24
|
+
/**
|
|
25
|
+
* Build a frozen risk report from evaluation results.
|
|
26
|
+
*/
|
|
27
|
+
build(score, matchedRules, thresholds, analyzedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
28
|
+
const reasons = matchedRules.map((rule) => rule.reason);
|
|
29
|
+
const level = resolveLevel(score, thresholds);
|
|
30
|
+
const report = {
|
|
31
|
+
score,
|
|
32
|
+
level,
|
|
33
|
+
reasons: Object.freeze([...reasons]),
|
|
34
|
+
matchedRules: Object.freeze([...matchedRules]),
|
|
35
|
+
analyzedAt
|
|
36
|
+
};
|
|
37
|
+
return Object.freeze(report);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/rules/RuleEvaluator.ts
|
|
42
|
+
var RuleEvaluator = class {
|
|
43
|
+
/**
|
|
44
|
+
* Evaluate all rules against the given signals.
|
|
45
|
+
* Rules are evaluated in registration order (exhaustive, no short-circuit).
|
|
46
|
+
*/
|
|
47
|
+
evaluate(rules, signals) {
|
|
48
|
+
const matched = [];
|
|
49
|
+
for (const rule of rules) {
|
|
50
|
+
if (rule.when(signals)) {
|
|
51
|
+
matched.push({
|
|
52
|
+
id: rule.id,
|
|
53
|
+
name: rule.name,
|
|
54
|
+
score: rule.score,
|
|
55
|
+
reason: rule.reason ?? rule.name
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return matched;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/score/ScoreCalculator.ts
|
|
64
|
+
var ScoreCalculator = class {
|
|
65
|
+
/**
|
|
66
|
+
* Sum scores from all matched rules.
|
|
67
|
+
*/
|
|
68
|
+
calculate(matchedRules) {
|
|
69
|
+
return matchedRules.reduce((total, rule) => total + rule.score, 0);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
function validateSignalValue(value) {
|
|
73
|
+
if (value === null) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const type = typeof value;
|
|
77
|
+
return type === "string" || type === "number" || type === "boolean";
|
|
78
|
+
}
|
|
79
|
+
function generateId() {
|
|
80
|
+
return randomUUID();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/signals/SignalStore.ts
|
|
84
|
+
var SignalStore = class {
|
|
85
|
+
signals = /* @__PURE__ */ new Map();
|
|
86
|
+
/**
|
|
87
|
+
* Set a signal value. Overwrites any existing value for the key.
|
|
88
|
+
*/
|
|
89
|
+
set(key, value) {
|
|
90
|
+
if (!validateSignalValue(value)) {
|
|
91
|
+
throw new TypeError(
|
|
92
|
+
`Invalid signal value for "${key}": signals must be string, number, boolean, or null`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
this.signals.set(key, value);
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get a signal value by key.
|
|
100
|
+
*/
|
|
101
|
+
get(key) {
|
|
102
|
+
return this.signals.get(key);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Check if a signal exists.
|
|
106
|
+
*/
|
|
107
|
+
has(key) {
|
|
108
|
+
return this.signals.has(key);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Returns a frozen snapshot of all signals.
|
|
112
|
+
*/
|
|
113
|
+
getAll() {
|
|
114
|
+
const snapshot = {};
|
|
115
|
+
for (const [key, value] of this.signals) {
|
|
116
|
+
snapshot[key] = value;
|
|
117
|
+
}
|
|
118
|
+
return Object.freeze(snapshot);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Clear all signals.
|
|
122
|
+
*/
|
|
123
|
+
clear() {
|
|
124
|
+
this.signals.clear();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// src/engine/RiskEngine.ts
|
|
129
|
+
var RiskEngine = class {
|
|
130
|
+
constructor(deps, thresholds = DEFAULT_RISK_LEVELS) {
|
|
131
|
+
this.deps = deps;
|
|
132
|
+
this.thresholds = thresholds;
|
|
133
|
+
}
|
|
134
|
+
deps;
|
|
135
|
+
rules = [];
|
|
136
|
+
thresholds;
|
|
137
|
+
/**
|
|
138
|
+
* Register a rule for evaluation.
|
|
139
|
+
*/
|
|
140
|
+
addRule(rule) {
|
|
141
|
+
this.rules.push(rule);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get all registered rules.
|
|
145
|
+
*/
|
|
146
|
+
getRules() {
|
|
147
|
+
return this.rules;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Run the full risk analysis pipeline.
|
|
151
|
+
*/
|
|
152
|
+
analyze() {
|
|
153
|
+
const signals = this.deps.signalStore.getAll();
|
|
154
|
+
const matchedRules = this.deps.ruleEvaluator.evaluate(this.rules, signals);
|
|
155
|
+
const score = this.deps.scoreCalculator.calculate(matchedRules);
|
|
156
|
+
return this.deps.reportBuilder.build(score, matchedRules, this.thresholds);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/rules/RuleBuilder.ts
|
|
161
|
+
var RuleBuilder = class {
|
|
162
|
+
/**
|
|
163
|
+
* Create a new rule from input configuration.
|
|
164
|
+
*/
|
|
165
|
+
static create(input) {
|
|
166
|
+
const rule = {
|
|
167
|
+
id: generateId(),
|
|
168
|
+
name: input.name,
|
|
169
|
+
score: input.score,
|
|
170
|
+
when: input.when,
|
|
171
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
172
|
+
...input.reason !== void 0 ? { reason: input.reason } : {}
|
|
173
|
+
};
|
|
174
|
+
return rule;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/engine/Guardian.ts
|
|
179
|
+
var Guardian = class {
|
|
180
|
+
signalStore;
|
|
181
|
+
riskEngine;
|
|
182
|
+
constructor(config = {}) {
|
|
183
|
+
const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;
|
|
184
|
+
this.signalStore = new SignalStore();
|
|
185
|
+
const deps = {
|
|
186
|
+
signalStore: this.signalStore,
|
|
187
|
+
ruleEvaluator: new RuleEvaluator(),
|
|
188
|
+
scoreCalculator: new ScoreCalculator(),
|
|
189
|
+
reportBuilder: new ReportBuilder()
|
|
190
|
+
};
|
|
191
|
+
this.riskEngine = new RiskEngine(deps, thresholds);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Add a signal value for risk evaluation.
|
|
195
|
+
*/
|
|
196
|
+
signal(key, value) {
|
|
197
|
+
this.signalStore.set(key, value);
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Register a rule. ID is auto-generated.
|
|
202
|
+
*/
|
|
203
|
+
rule(input) {
|
|
204
|
+
const rule = RuleBuilder.create(input);
|
|
205
|
+
this.riskEngine.addRule(rule);
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Run risk analysis and return an immutable report.
|
|
210
|
+
*/
|
|
211
|
+
analyze() {
|
|
212
|
+
return this.riskEngine.analyze();
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Clear all signals. Rules persist across resets.
|
|
216
|
+
*/
|
|
217
|
+
reset() {
|
|
218
|
+
this.signalStore.clear();
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export { DEFAULT_RISK_LEVELS, Guardian, RiskEngine, RuleBuilder, RuleEvaluator, ScoreCalculator, SignalStore, resolveLevel };
|
|
224
|
+
//# sourceMappingURL=index.js.map
|
|
225
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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/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 { 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 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\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 * 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 persist across resets.\n */\n reset(): this {\n this.signalStore.clear();\n return this;\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "guardian-risk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Configurable risk decision engine using signals, rules, and scoring",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"risk",
|
|
32
|
+
"risk-scoring",
|
|
33
|
+
"rules-engine",
|
|
34
|
+
"fraud-detection",
|
|
35
|
+
"bot-detection",
|
|
36
|
+
"security",
|
|
37
|
+
"typescript"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"tsup": "^8.3.5",
|
|
45
|
+
"typescript": "^5.7.2",
|
|
46
|
+
"vitest": "^2.1.8"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:coverage": "vitest run --coverage",
|
|
52
|
+
"typecheck": "tsc --noEmit"
|
|
53
|
+
}
|
|
54
|
+
}
|