musubi-sdd 3.8.0 → 3.9.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/bin/musubi-validate.js +234 -0
- package/package.json +1 -1
- package/src/orchestration/guardrails/base-guardrail.js +358 -0
- package/src/orchestration/guardrails/guardrail-rules.js +532 -0
- package/src/orchestration/guardrails/index.js +61 -0
- package/src/orchestration/guardrails/input-guardrail.js +378 -0
- package/src/orchestration/guardrails/output-guardrail.js +514 -0
- package/src/orchestration/guardrails/safety-check.js +471 -0
- package/src/orchestration/index.js +43 -0
package/bin/musubi-validate.js
CHANGED
|
@@ -11,12 +11,20 @@
|
|
|
11
11
|
* musubi-validate article <1-9> # Validate specific article
|
|
12
12
|
* musubi-validate gates # Validate Phase -1 Gates
|
|
13
13
|
* musubi-validate complexity # Validate complexity limits
|
|
14
|
+
* musubi-validate guardrails # Validate content with guardrails
|
|
14
15
|
* musubi-validate all # Run all validations
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
const { Command } = require('commander');
|
|
18
19
|
const chalk = require('chalk');
|
|
19
20
|
const ConstitutionValidator = require('../src/validators/constitution');
|
|
21
|
+
const {
|
|
22
|
+
createInputGuardrail,
|
|
23
|
+
createOutputGuardrail,
|
|
24
|
+
createSafetyCheckGuardrail,
|
|
25
|
+
GuardrailChain,
|
|
26
|
+
SafetyLevel
|
|
27
|
+
} = require('../src/orchestration/guardrails');
|
|
20
28
|
|
|
21
29
|
const program = new Command();
|
|
22
30
|
|
|
@@ -107,6 +115,134 @@ program
|
|
|
107
115
|
}
|
|
108
116
|
});
|
|
109
117
|
|
|
118
|
+
// Guardrails validation
|
|
119
|
+
program
|
|
120
|
+
.command('guardrails')
|
|
121
|
+
.description('Validate content with input/output guardrails')
|
|
122
|
+
.argument('[content]', 'Content to validate (or use --file)')
|
|
123
|
+
.option('--file <path>', 'Read content from file')
|
|
124
|
+
.option('-t, --type <type>', 'Guardrail type (input|output|safety)', 'input')
|
|
125
|
+
.option('-l, --level <level>', 'Safety level (basic|standard|strict|paranoid)', 'standard')
|
|
126
|
+
.option('--constitutional', 'Enable constitutional compliance checks')
|
|
127
|
+
.option('--redact', 'Enable redaction for output guardrails')
|
|
128
|
+
.option('-f, --format <type>', 'Output format (console|json)', 'console')
|
|
129
|
+
.action(async (content, options) => {
|
|
130
|
+
try {
|
|
131
|
+
// Get content from argument, file, or stdin
|
|
132
|
+
let inputContent = content;
|
|
133
|
+
|
|
134
|
+
if (options.file) {
|
|
135
|
+
const fs = require('fs');
|
|
136
|
+
const path = require('path');
|
|
137
|
+
const filePath = path.resolve(process.cwd(), options.file);
|
|
138
|
+
inputContent = fs.readFileSync(filePath, 'utf-8');
|
|
139
|
+
} else if (!inputContent && !process.stdin.isTTY) {
|
|
140
|
+
// Read from stdin
|
|
141
|
+
const chunks = [];
|
|
142
|
+
for await (const chunk of process.stdin) {
|
|
143
|
+
chunks.push(chunk);
|
|
144
|
+
}
|
|
145
|
+
inputContent = Buffer.concat(chunks).toString('utf-8');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!inputContent) {
|
|
149
|
+
console.error(chalk.red('✗ No content provided. Use --file or pipe content.'));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let guardrail;
|
|
154
|
+
const guardrailType = options.type.toLowerCase();
|
|
155
|
+
|
|
156
|
+
switch (guardrailType) {
|
|
157
|
+
case 'input':
|
|
158
|
+
guardrail = createInputGuardrail('userInput', {
|
|
159
|
+
sanitize: true
|
|
160
|
+
});
|
|
161
|
+
break;
|
|
162
|
+
|
|
163
|
+
case 'output':
|
|
164
|
+
guardrail = createOutputGuardrail(options.redact ? 'redact' : 'safe');
|
|
165
|
+
break;
|
|
166
|
+
|
|
167
|
+
case 'safety':
|
|
168
|
+
guardrail = createSafetyCheckGuardrail(options.level, {
|
|
169
|
+
enforceConstitution: options.constitutional
|
|
170
|
+
});
|
|
171
|
+
break;
|
|
172
|
+
|
|
173
|
+
default:
|
|
174
|
+
console.error(chalk.red(`✗ Unknown guardrail type: ${guardrailType}`));
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(chalk.dim(`\n🛡️ Running ${guardrailType} guardrail validation...\n`));
|
|
179
|
+
|
|
180
|
+
const result = await guardrail.run(inputContent);
|
|
181
|
+
|
|
182
|
+
displayGuardrailResults(result, options);
|
|
183
|
+
process.exit(result.passed ? 0 : 1);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error(chalk.red('✗ Guardrail validation error:'), error.message);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Guardrails chain validation
|
|
191
|
+
program
|
|
192
|
+
.command('guardrails-chain')
|
|
193
|
+
.description('Run a chain of guardrails on content')
|
|
194
|
+
.argument('[content]', 'Content to validate')
|
|
195
|
+
.option('--file <path>', 'Read content from file')
|
|
196
|
+
.option('--parallel', 'Run guardrails in parallel')
|
|
197
|
+
.option('--stop-on-failure', 'Stop on first failure')
|
|
198
|
+
.option('-f, --format <type>', 'Output format (console|json)', 'console')
|
|
199
|
+
.action(async (content, options) => {
|
|
200
|
+
try {
|
|
201
|
+
// Get content
|
|
202
|
+
let inputContent = content;
|
|
203
|
+
|
|
204
|
+
if (options.file) {
|
|
205
|
+
const fs = require('fs');
|
|
206
|
+
const path = require('path');
|
|
207
|
+
const filePath = path.resolve(process.cwd(), options.file);
|
|
208
|
+
inputContent = fs.readFileSync(filePath, 'utf-8');
|
|
209
|
+
} else if (!inputContent && !process.stdin.isTTY) {
|
|
210
|
+
const chunks = [];
|
|
211
|
+
for await (const chunk of process.stdin) {
|
|
212
|
+
chunks.push(chunk);
|
|
213
|
+
}
|
|
214
|
+
inputContent = Buffer.concat(chunks).toString('utf-8');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!inputContent) {
|
|
218
|
+
console.error(chalk.red('✗ No content provided. Use --file or pipe content.'));
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Create guardrail chain
|
|
223
|
+
const chain = new GuardrailChain({
|
|
224
|
+
name: 'ValidationChain',
|
|
225
|
+
parallel: options.parallel || false,
|
|
226
|
+
stopOnFirstFailure: options.stopOnFailure || false
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Add default guardrails
|
|
230
|
+
chain.add(createInputGuardrail('security'));
|
|
231
|
+
chain.add(createSafetyCheckGuardrail('standard'));
|
|
232
|
+
chain.add(createOutputGuardrail('safe'));
|
|
233
|
+
|
|
234
|
+
console.log(chalk.dim('\n🔗 Running guardrail chain validation...\n'));
|
|
235
|
+
|
|
236
|
+
const result = await chain.run(inputContent);
|
|
237
|
+
|
|
238
|
+
displayChainResults(result, options);
|
|
239
|
+
process.exit(result.passed ? 0 : 1);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error(chalk.red('✗ Guardrail chain error:'), error.message);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
110
246
|
// Score validation (numeric score output for CI/CD)
|
|
111
247
|
program
|
|
112
248
|
.command('score')
|
|
@@ -345,6 +481,104 @@ function displayMarkdown(title, results) {
|
|
|
345
481
|
}
|
|
346
482
|
}
|
|
347
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Display guardrail validation results
|
|
486
|
+
*/
|
|
487
|
+
function displayGuardrailResults(result, options) {
|
|
488
|
+
if (options.format === 'json') {
|
|
489
|
+
console.log(JSON.stringify(result, null, 2));
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
console.log(chalk.bold('Guardrail Validation Results'));
|
|
494
|
+
console.log(chalk.bold('━'.repeat(50)));
|
|
495
|
+
|
|
496
|
+
if (result.passed) {
|
|
497
|
+
console.log(chalk.green(`\n✓ PASSED - ${result.guardrailName}\n`));
|
|
498
|
+
} else {
|
|
499
|
+
console.log(chalk.red(`\n✗ FAILED - ${result.guardrailName}\n`));
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (result.message) {
|
|
503
|
+
console.log(chalk.dim(`Message: ${result.message}`));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
console.log(chalk.dim(`Execution time: ${result.executionTimeMs}ms\n`));
|
|
507
|
+
|
|
508
|
+
if (result.violations && result.violations.length > 0) {
|
|
509
|
+
console.log(chalk.bold.red('Violations:'));
|
|
510
|
+
result.violations.forEach(violation => {
|
|
511
|
+
const severityIcon = violation.severity === 'error' ? '✗' :
|
|
512
|
+
violation.severity === 'warning' ? '⚠' : 'ℹ';
|
|
513
|
+
const severityColor = violation.severity === 'error' ? chalk.red :
|
|
514
|
+
violation.severity === 'warning' ? chalk.yellow : chalk.blue;
|
|
515
|
+
console.log(severityColor(` ${severityIcon} [${violation.code}] ${violation.message}`));
|
|
516
|
+
});
|
|
517
|
+
console.log();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Show metadata if redaction was applied
|
|
521
|
+
if (result.metadata?.redactionApplied) {
|
|
522
|
+
console.log(chalk.bold.blue('Redaction Applied:'));
|
|
523
|
+
console.log(chalk.dim(` ${result.metadata.redactionCount} item(s) redacted`));
|
|
524
|
+
if (result.metadata.redactions) {
|
|
525
|
+
result.metadata.redactions.forEach(r => {
|
|
526
|
+
console.log(chalk.dim(` - ${r.type}: ${r.count}`));
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
console.log();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Show quality scores if available
|
|
533
|
+
if (result.metadata?.qualityScores) {
|
|
534
|
+
console.log(chalk.bold.blue('Quality Scores:'));
|
|
535
|
+
Object.entries(result.metadata.qualityScores).forEach(([name, score]) => {
|
|
536
|
+
console.log(chalk.dim(` ${name}: ${(score * 100).toFixed(1)}%`));
|
|
537
|
+
});
|
|
538
|
+
console.log();
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Display guardrail chain results
|
|
544
|
+
*/
|
|
545
|
+
function displayChainResults(result, options) {
|
|
546
|
+
if (options.format === 'json') {
|
|
547
|
+
console.log(JSON.stringify(result, null, 2));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
console.log(chalk.bold('Guardrail Chain Results'));
|
|
552
|
+
console.log(chalk.bold('━'.repeat(50)));
|
|
553
|
+
|
|
554
|
+
if (result.passed) {
|
|
555
|
+
console.log(chalk.green(`\n✓ PASSED - ${result.chainName}\n`));
|
|
556
|
+
} else {
|
|
557
|
+
console.log(chalk.red(`\n✗ FAILED - ${result.chainName}\n`));
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
console.log(chalk.dim(`Guardrails executed: ${result.executedCount}/${result.guardrailCount}`));
|
|
561
|
+
console.log(chalk.dim(`Total time: ${result.executionTimeMs}ms\n`));
|
|
562
|
+
|
|
563
|
+
// Show individual guardrail results
|
|
564
|
+
console.log(chalk.bold('Individual Results:'));
|
|
565
|
+
result.results.forEach((r, index) => {
|
|
566
|
+
const icon = r.passed ? chalk.green('✓') : chalk.red('✗');
|
|
567
|
+
console.log(` ${icon} ${r.guardrailName} (${r.executionTimeMs}ms)`);
|
|
568
|
+
});
|
|
569
|
+
console.log();
|
|
570
|
+
|
|
571
|
+
// Show all violations
|
|
572
|
+
if (result.violations && result.violations.length > 0) {
|
|
573
|
+
console.log(chalk.bold.red('All Violations:'));
|
|
574
|
+
result.violations.forEach(violation => {
|
|
575
|
+
const severityColor = violation.severity === 'error' ? chalk.red : chalk.yellow;
|
|
576
|
+
console.log(severityColor(` • [${violation.code}] ${violation.message}`));
|
|
577
|
+
});
|
|
578
|
+
console.log();
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
348
582
|
// Parse arguments
|
|
349
583
|
program.parse(process.argv);
|
|
350
584
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "musubi-sdd",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
4
4
|
"description": "Ultimate Specification Driven Development Tool with 27 Agents for 7 AI Coding Platforms + MCP Integration (Claude Code, GitHub Copilot, Cursor, Gemini CLI, Windsurf, Codex, Qwen Code)",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Base Guardrail class for MUSUBI Orchestration
|
|
3
|
+
*
|
|
4
|
+
* Guardrails provide safety checks for agent inputs and outputs.
|
|
5
|
+
* Inspired by OpenAI Agents SDK guardrails pattern.
|
|
6
|
+
*
|
|
7
|
+
* @module orchestration/guardrails/base-guardrail
|
|
8
|
+
* @version 3.9.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Guardrail execution result
|
|
15
|
+
* @typedef {Object} GuardrailResult
|
|
16
|
+
* @property {boolean} passed - Whether the guardrail check passed
|
|
17
|
+
* @property {string} guardrailName - Name of the guardrail that was executed
|
|
18
|
+
* @property {string} [message] - Optional message describing the result
|
|
19
|
+
* @property {Array<GuardrailViolation>} violations - List of violations found
|
|
20
|
+
* @property {Object} [metadata] - Additional metadata from the check
|
|
21
|
+
* @property {number} executionTimeMs - Time taken to execute the guardrail
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Guardrail violation details
|
|
26
|
+
* @typedef {Object} GuardrailViolation
|
|
27
|
+
* @property {string} code - Violation code (e.g., 'PII_DETECTED', 'PROHIBITED_CONTENT')
|
|
28
|
+
* @property {string} message - Human-readable description
|
|
29
|
+
* @property {string} severity - 'error' | 'warning' | 'info'
|
|
30
|
+
* @property {Object} [context] - Additional context about the violation
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Guardrail configuration options
|
|
35
|
+
* @typedef {Object} GuardrailConfig
|
|
36
|
+
* @property {string} name - Unique name for the guardrail
|
|
37
|
+
* @property {string} [description] - Description of what the guardrail checks
|
|
38
|
+
* @property {boolean} [enabled=true] - Whether the guardrail is enabled
|
|
39
|
+
* @property {boolean} [failFast=false] - Stop on first violation
|
|
40
|
+
* @property {string} [severity='error'] - Default severity level
|
|
41
|
+
* @property {Object} [options] - Guardrail-specific options
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Tripwire exception - thrown when guardrail fails and tripwire is enabled
|
|
46
|
+
* @class
|
|
47
|
+
*/
|
|
48
|
+
class GuardrailTripwireException extends Error {
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} message - Error message
|
|
51
|
+
* @param {GuardrailResult} result - The guardrail result that triggered the tripwire
|
|
52
|
+
*/
|
|
53
|
+
constructor(message, result) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = 'GuardrailTripwireException';
|
|
56
|
+
this.result = result;
|
|
57
|
+
this.violations = result.violations;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Base class for all guardrails
|
|
63
|
+
* @abstract
|
|
64
|
+
*/
|
|
65
|
+
class BaseGuardrail {
|
|
66
|
+
/**
|
|
67
|
+
* @param {GuardrailConfig} config - Guardrail configuration
|
|
68
|
+
*/
|
|
69
|
+
constructor(config) {
|
|
70
|
+
if (new.target === BaseGuardrail) {
|
|
71
|
+
throw new Error('BaseGuardrail is abstract and cannot be instantiated directly');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.name = config.name || this.constructor.name;
|
|
75
|
+
this.description = config.description || '';
|
|
76
|
+
this.enabled = config.enabled !== false;
|
|
77
|
+
this.failFast = config.failFast || false;
|
|
78
|
+
this.defaultSeverity = config.severity || 'error';
|
|
79
|
+
this.options = config.options || {};
|
|
80
|
+
|
|
81
|
+
// Tripwire: if true, throws exception on failure instead of returning result
|
|
82
|
+
this.tripwireEnabled = config.tripwireEnabled || false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Execute the guardrail check
|
|
87
|
+
* @abstract
|
|
88
|
+
* @param {*} input - Input to validate
|
|
89
|
+
* @param {Object} [context] - Execution context
|
|
90
|
+
* @returns {Promise<GuardrailResult>}
|
|
91
|
+
*/
|
|
92
|
+
async check(input, context = {}) {
|
|
93
|
+
throw new Error('Subclasses must implement check() method');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Run the guardrail with timing and error handling
|
|
98
|
+
* @param {*} input - Input to validate
|
|
99
|
+
* @param {Object} [context] - Execution context
|
|
100
|
+
* @returns {Promise<GuardrailResult>}
|
|
101
|
+
*/
|
|
102
|
+
async run(input, context = {}) {
|
|
103
|
+
if (!this.enabled) {
|
|
104
|
+
return this.createResult(true, [], 'Guardrail is disabled');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const startTime = Date.now();
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const result = await this.check(input, context);
|
|
111
|
+
result.executionTimeMs = Date.now() - startTime;
|
|
112
|
+
result.guardrailName = this.name;
|
|
113
|
+
|
|
114
|
+
// Handle tripwire
|
|
115
|
+
if (this.tripwireEnabled && !result.passed) {
|
|
116
|
+
throw new GuardrailTripwireException(
|
|
117
|
+
`Guardrail '${this.name}' triggered tripwire: ${result.message || 'Validation failed'}`,
|
|
118
|
+
result
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return result;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error instanceof GuardrailTripwireException) {
|
|
125
|
+
throw error; // Re-throw tripwire exceptions
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Wrap unexpected errors
|
|
129
|
+
return this.createResult(
|
|
130
|
+
false,
|
|
131
|
+
[{
|
|
132
|
+
code: 'GUARDRAIL_ERROR',
|
|
133
|
+
message: error.message,
|
|
134
|
+
severity: 'error',
|
|
135
|
+
context: { errorName: error.name, stack: error.stack }
|
|
136
|
+
}],
|
|
137
|
+
`Guardrail execution failed: ${error.message}`,
|
|
138
|
+
Date.now() - startTime
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create a standardized guardrail result
|
|
145
|
+
* @protected
|
|
146
|
+
* @param {boolean} passed - Whether the check passed
|
|
147
|
+
* @param {Array<GuardrailViolation>} [violations=[]] - List of violations
|
|
148
|
+
* @param {string} [message] - Optional message
|
|
149
|
+
* @param {number} [executionTimeMs=0] - Execution time
|
|
150
|
+
* @param {Object} [metadata] - Additional metadata
|
|
151
|
+
* @returns {GuardrailResult}
|
|
152
|
+
*/
|
|
153
|
+
createResult(passed, violations = [], message = undefined, executionTimeMs = 0, metadata = {}) {
|
|
154
|
+
return {
|
|
155
|
+
passed,
|
|
156
|
+
guardrailName: this.name,
|
|
157
|
+
message,
|
|
158
|
+
violations,
|
|
159
|
+
metadata,
|
|
160
|
+
executionTimeMs
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create a violation object
|
|
166
|
+
* @protected
|
|
167
|
+
* @param {string} code - Violation code
|
|
168
|
+
* @param {string} message - Human-readable message
|
|
169
|
+
* @param {string} [severity] - Severity level
|
|
170
|
+
* @param {Object} [context] - Additional context
|
|
171
|
+
* @returns {GuardrailViolation}
|
|
172
|
+
*/
|
|
173
|
+
createViolation(code, message, severity = null, context = {}) {
|
|
174
|
+
return {
|
|
175
|
+
code,
|
|
176
|
+
message,
|
|
177
|
+
severity: severity || this.defaultSeverity,
|
|
178
|
+
context
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Enable the guardrail
|
|
184
|
+
*/
|
|
185
|
+
enable() {
|
|
186
|
+
this.enabled = true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Disable the guardrail
|
|
191
|
+
*/
|
|
192
|
+
disable() {
|
|
193
|
+
this.enabled = false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Enable tripwire mode
|
|
198
|
+
*/
|
|
199
|
+
enableTripwire() {
|
|
200
|
+
this.tripwireEnabled = true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Disable tripwire mode
|
|
205
|
+
*/
|
|
206
|
+
disableTripwire() {
|
|
207
|
+
this.tripwireEnabled = false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get guardrail info
|
|
212
|
+
* @returns {Object}
|
|
213
|
+
*/
|
|
214
|
+
getInfo() {
|
|
215
|
+
return {
|
|
216
|
+
name: this.name,
|
|
217
|
+
description: this.description,
|
|
218
|
+
enabled: this.enabled,
|
|
219
|
+
failFast: this.failFast,
|
|
220
|
+
tripwireEnabled: this.tripwireEnabled,
|
|
221
|
+
defaultSeverity: this.defaultSeverity
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Guardrail chain for running multiple guardrails
|
|
228
|
+
*/
|
|
229
|
+
class GuardrailChain {
|
|
230
|
+
/**
|
|
231
|
+
* @param {Object} [options] - Chain options
|
|
232
|
+
* @param {boolean} [options.parallel=false] - Run guardrails in parallel
|
|
233
|
+
* @param {boolean} [options.stopOnFirstFailure=false] - Stop on first failure
|
|
234
|
+
* @param {string} [options.name='GuardrailChain'] - Chain name
|
|
235
|
+
*/
|
|
236
|
+
constructor(options = {}) {
|
|
237
|
+
this.name = options.name || 'GuardrailChain';
|
|
238
|
+
this.parallel = options.parallel || false;
|
|
239
|
+
this.stopOnFirstFailure = options.stopOnFirstFailure || false;
|
|
240
|
+
this.guardrails = [];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Add a guardrail to the chain
|
|
245
|
+
* @param {BaseGuardrail} guardrail - Guardrail to add
|
|
246
|
+
* @returns {GuardrailChain} this for chaining
|
|
247
|
+
*/
|
|
248
|
+
add(guardrail) {
|
|
249
|
+
if (!(guardrail instanceof BaseGuardrail)) {
|
|
250
|
+
throw new Error('Can only add BaseGuardrail instances to the chain');
|
|
251
|
+
}
|
|
252
|
+
this.guardrails.push(guardrail);
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Add multiple guardrails to the chain
|
|
258
|
+
* @param {Array<BaseGuardrail>} guardrails - Guardrails to add
|
|
259
|
+
* @returns {GuardrailChain} this for chaining
|
|
260
|
+
*/
|
|
261
|
+
addAll(guardrails) {
|
|
262
|
+
guardrails.forEach(g => this.add(g));
|
|
263
|
+
return this;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Run all guardrails in the chain
|
|
268
|
+
* @param {*} input - Input to validate
|
|
269
|
+
* @param {Object} [context] - Execution context
|
|
270
|
+
* @returns {Promise<Object>} Combined result
|
|
271
|
+
*/
|
|
272
|
+
async run(input, context = {}) {
|
|
273
|
+
const startTime = Date.now();
|
|
274
|
+
const results = [];
|
|
275
|
+
const allViolations = [];
|
|
276
|
+
let overallPassed = true;
|
|
277
|
+
|
|
278
|
+
if (this.parallel) {
|
|
279
|
+
// Parallel execution with optional early termination
|
|
280
|
+
const promises = this.guardrails.map(g => g.run(input, context));
|
|
281
|
+
|
|
282
|
+
if (this.stopOnFirstFailure) {
|
|
283
|
+
// Use Promise.race pattern for early termination
|
|
284
|
+
const settledResults = await Promise.allSettled(promises);
|
|
285
|
+
|
|
286
|
+
for (const settled of settledResults) {
|
|
287
|
+
if (settled.status === 'fulfilled') {
|
|
288
|
+
const result = settled.value;
|
|
289
|
+
results.push(result);
|
|
290
|
+
|
|
291
|
+
if (!result.passed) {
|
|
292
|
+
overallPassed = false;
|
|
293
|
+
allViolations.push(...result.violations);
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
// Handle rejected promise (tripwire or error)
|
|
297
|
+
throw settled.reason;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
const resolvedResults = await Promise.all(promises);
|
|
302
|
+
for (const result of resolvedResults) {
|
|
303
|
+
results.push(result);
|
|
304
|
+
if (!result.passed) {
|
|
305
|
+
overallPassed = false;
|
|
306
|
+
allViolations.push(...result.violations);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
// Sequential execution
|
|
312
|
+
for (const guardrail of this.guardrails) {
|
|
313
|
+
const result = await guardrail.run(input, context);
|
|
314
|
+
results.push(result);
|
|
315
|
+
|
|
316
|
+
if (!result.passed) {
|
|
317
|
+
overallPassed = false;
|
|
318
|
+
allViolations.push(...result.violations);
|
|
319
|
+
|
|
320
|
+
if (this.stopOnFirstFailure) {
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
passed: overallPassed,
|
|
329
|
+
chainName: this.name,
|
|
330
|
+
results,
|
|
331
|
+
violations: allViolations,
|
|
332
|
+
guardrailCount: this.guardrails.length,
|
|
333
|
+
executedCount: results.length,
|
|
334
|
+
executionTimeMs: Date.now() - startTime
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get list of guardrails in the chain
|
|
340
|
+
* @returns {Array<Object>}
|
|
341
|
+
*/
|
|
342
|
+
getGuardrails() {
|
|
343
|
+
return this.guardrails.map(g => g.getInfo());
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Clear all guardrails from the chain
|
|
348
|
+
*/
|
|
349
|
+
clear() {
|
|
350
|
+
this.guardrails = [];
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
module.exports = {
|
|
355
|
+
BaseGuardrail,
|
|
356
|
+
GuardrailChain,
|
|
357
|
+
GuardrailTripwireException
|
|
358
|
+
};
|