A Risk Recommendation System built on nx-rules for analyzing data exposure risks and producing actionable recommendations
npm install x-risk-recommendationA production-grade recommendation system built on nx-rules for analyzing data exposure risks and producing actionable recommendations.


Organizations track data exposure risks through "risk counts"—aggregated metrics describing what sensitive information was found, how much, and its severity. This system determines whether risks can be automatically remediated via existing policies (like Microsoft Information Protection or Google Workspace labels) or require manual review.
```
┌─────────────────────────────────────────────────────────────────┐
│ Risk Recommendation System │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ Input │────▶│ nx-rules │────▶│ Output │ │
│ │RiskCount │ │ Engine │ │ Recommendation │ │
│ └──────────┘ └──────┬───────┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ RuleSet │ │
│ │ - route │ │
│ │ - actions │ │
│ │ - templates │ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
- 🎯 Simple Decision Logic - Two paths: policy (automated) or recommendation (manual)
- 📝 Template-Based Output - Configurable recommendation content
- 🔍 Full Traceability - Track exactly which rule matched and why
- 📊 JSON-Based Rules - Business logic as data, not code
- 🔧 Extensible - Add new templates and rules without code changes
`bash`
npm install
`typescript
import { createRecommendationEngine, type RiskCount } from 'risk-recommendation-system';
// Create the engine
const engine = createRecommendationEngine();
// Evaluate a risk with an external label → Policy recommendation
const riskWithLabel: RiskCount = {
riskType: 'PII_EXPOSURE',
count: 15,
contentLabel: 'SSN',
externalLabel: 'Confidential', // Has label mapping
severity: 'high'
};
const result1 = await engine.evaluate(riskWithLabel);
console.log(result1.recommendation.action); // 'policy'
console.log(result1.recommendation.content);
// "Apply Confidential policy to automatically remediate 15 SSN exposure(s). Severity: high | Risk Type: PII_EXPOSURE"
// Evaluate a risk without an external label → Manual recommendation
const riskWithoutLabel: RiskCount = {
riskType: 'CREDENTIAL_LEAK',
count: 8,
contentLabel: 'API Key',
severity: 'critical'
// No externalLabel
};
const result2 = await engine.evaluate(riskWithoutLabel);
console.log(result2.recommendation.action); // 'recommendation'
console.log(result2.recommendation.content);
// "Review and manually label 8 API Key item(s) flagged as CREDENTIAL_LEAK. Severity: critical. No automated policy available—manual classification required."
`
`typescript`
interface RiskCount {
riskType: string; // Category of risk (e.g., "PII_EXPOSURE")
count: number; // Number of instances detected
contentLabel: string; // What information is at risk (e.g., "SSN")
externalLabel?: string; // Mapped MIP/Google label, if any
severity: 'low' | 'medium' | 'high' | 'critical';
}
`typescript`
interface Recommendation {
riskInput: RiskCount; // Original input (preserved)
action: 'policy' | 'recommendation';
content: string; // Human-readable recommendation
}
The system implements a simple routing decision:
`
IF risk has an external label mapped
THEN action = "policy"
AND content = policy remediation instructions
IF risk has no external label mapped
THEN action = "recommendation"
AND content = manual review instructions
`
Two rules are configured:
Routes to automated remediation when externalLabel exists:
`json`
{
"ruleId": "risk-with-label-to-policy",
"priority": 100,
"route": {
"exists": { "path": "input.externalLabel" }
},
"actions": {
"onMatch": [{
"type": "transform",
"args": { "action": "policy", "template": "policy-recommendation" }
}]
}
}
Routes to manual review when no externalLabel:
`json`
{
"ruleId": "risk-without-label-to-recommendation",
"priority": 90,
"route": {
"not": { "exists": { "path": "input.externalLabel" } }
},
"actions": {
"onMatch": [{
"type": "transform",
"args": { "action": "recommendation", "template": "manual-recommendation" }
}]
}
}
Apply {{externalLabel}} policy to automatically remediate {{count}} {{contentLabel}} exposure(s).
Severity: {{severity}} | Risk Type: {{riskType}}
`$3
`
Review and manually label {{count}} {{contentLabel}} item(s) flagged as {{riskType}}.
Severity: {{severity}}. No automated policy available—manual classification required.
`$3
`typescript
const result = await engine.evaluate(risk, {
tenantId: 'tenant-123',
environment: 'production'
});
// Only rules matching this scope (or global rules) will be evaluated
`Configuration
`typescript
const engine = createRecommendationEngine({
matchPolicy: 'FIRST_MATCH', // Stop after first rule matches
conflictPolicy: 'ERROR', // Only one rule should match
trace: true // Enable for audit trail
});
`Tracing
Every evaluation includes trace events for debugging and auditing:
`typescript
const result = await engine.evaluate(risk);if (result.trace) {
result.trace.forEach(event => {
console.log(event.type, event.ruleId, event.timestamp);
});
}
`Trace event types:
-
evaluation_start / evaluation_end
- route_end
- condition_end
- action_start / action_end
- write
- errorAPI Reference
$3
Creates a new recommendation engine.
`typescript
const engine = createRecommendationEngine({
matchPolicy?: 'FIRST_MATCH' | 'ALL_MATCH',
conflictPolicy?: 'FIRST_WRITE_WINS' | 'LAST_WRITE_WINS' | 'MERGE' | 'ERROR',
trace?: boolean
});
`$3
Evaluates a risk and produces a recommendation.
`typescript
const result = await engine.evaluate(riskCount);
// Returns: RecommendationResult
`$3
Returns all configured rules.
$3
Adds a new rule to the engine.
$3
Quick helper function for one-time evaluations.
`typescript
import { evaluateRisk } from 'risk-recommendation-system';const result = await evaluateRisk(riskCount);
`Testing
`bash
npm test
`Development
`bash
Run demo
npm run devBuild
npm run buildWatch tests
npm run test:watch
`Project Structure
`
risk-recommendation-system/
├── src/
│ ├── index.ts # Main exports
│ ├── types.ts # TypeScript interfaces
│ ├── engine.ts # Rule engine implementation
│ ├── demo.ts # Demo script
│ ├── templates/
│ │ └── index.ts # Template registry
│ ├── functions/
│ │ └── render.ts # Render function
│ └── rules/
│ └── index.ts # Rule definitions
├── tests/
│ └── recommendation.test.ts
├── package.json
├── tsconfig.json
└── vitest.config.ts
``| To Add... | Configure... |
|-----------|--------------|
| New risk type | No change needed (generic) |
| New severity level | No change needed (pass-through) |
| New recommendation path | Add new rule with route condition |
| New template | Register template, reference in rule action |
| Custom content logic | Register new function, reference in action |
| Condition | Behavior |
|-----------|----------|
| No rule matches | Returns error: "No recommendation path for input" |
| Template not found | Returns error: "Template {id} not registered" |
| Missing required field | Validation error before evaluation |
MIT
- nx-rules - Production-grade rules engine for Node.js/TypeScript