@unrdf/hooks 5.0.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.
Files changed (33) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/package.json +70 -0
  4. package/src/hooks/builtin-hooks.mjs +296 -0
  5. package/src/hooks/condition-cache.mjs +109 -0
  6. package/src/hooks/condition-evaluator.mjs +722 -0
  7. package/src/hooks/define-hook.mjs +211 -0
  8. package/src/hooks/effect-sandbox-worker.mjs +170 -0
  9. package/src/hooks/effect-sandbox.mjs +517 -0
  10. package/src/hooks/file-resolver.mjs +387 -0
  11. package/src/hooks/hook-chain-compiler.mjs +236 -0
  12. package/src/hooks/hook-executor-batching.mjs +277 -0
  13. package/src/hooks/hook-executor.mjs +465 -0
  14. package/src/hooks/hook-management.mjs +202 -0
  15. package/src/hooks/hook-scheduler.mjs +413 -0
  16. package/src/hooks/knowledge-hook-engine.mjs +358 -0
  17. package/src/hooks/knowledge-hook-manager.mjs +269 -0
  18. package/src/hooks/observability.mjs +531 -0
  19. package/src/hooks/policy-pack.mjs +572 -0
  20. package/src/hooks/quad-pool.mjs +249 -0
  21. package/src/hooks/quality-metrics.mjs +544 -0
  22. package/src/hooks/security/error-sanitizer.mjs +257 -0
  23. package/src/hooks/security/path-validator.mjs +194 -0
  24. package/src/hooks/security/sandbox-restrictions.mjs +331 -0
  25. package/src/hooks/telemetry.mjs +167 -0
  26. package/src/index.mjs +101 -0
  27. package/src/security/sandbox/browser-executor.mjs +220 -0
  28. package/src/security/sandbox/detector.mjs +342 -0
  29. package/src/security/sandbox/isolated-vm-executor.mjs +373 -0
  30. package/src/security/sandbox/vm2-executor.mjs +217 -0
  31. package/src/security/sandbox/worker-executor-runtime.mjs +74 -0
  32. package/src/security/sandbox/worker-executor.mjs +212 -0
  33. package/src/security/sandbox-adapter.mjs +141 -0
@@ -0,0 +1,544 @@
1
+ /**
2
+ * @file Lean Six Sigma Quality Metrics for Knowledge Hooks
3
+ * @module hooks/quality-metrics
4
+ *
5
+ * @description
6
+ * Statistical process control and DMAIC workflow support for quality hooks:
7
+ * - quality-gate: Enforce quality checkpoints (Control)
8
+ * - defect-detection: Statistical outlier detection (Measure)
9
+ * - continuous-improvement: Periodic optimization (Improve)
10
+ * - spc-control: Statistical process control charts (Control)
11
+ * - capability-analysis: Cp/Cpk metrics (Analyze)
12
+ * - root-cause: 5 Whys automation (Analyze)
13
+ * - kaizen-event: Improvement opportunity (Improve)
14
+ * - audit-trail: Compliance logging (Define)
15
+ */
16
+
17
+ import { z } from 'zod';
18
+
19
+ /**
20
+ * @typedef {Object} QualityMetric
21
+ * @property {string} name - Metric name
22
+ * @property {number} value - Current value
23
+ * @property {number} target - Target value
24
+ * @property {number} ucl - Upper control limit
25
+ * @property {number} lcl - Lower control limit
26
+ * @property {string} unit - Unit of measure
27
+ */
28
+
29
+ /**
30
+ * @typedef {Object} DefectRecord
31
+ * @property {string} id - Defect ID
32
+ * @property {string} type - Defect type
33
+ * @property {Date} timestamp - Detection time
34
+ * @property {string} source - Source of defect
35
+ * @property {string} severity - critical, major, minor
36
+ * @property {object} context - Additional context
37
+ */
38
+
39
+ /**
40
+ * Quality gate configuration schema
41
+ * POKA-YOKE: Validates operator-threshold compatibility at parse time
42
+ */
43
+ export const QualityGateSchema = z
44
+ .object({
45
+ name: z.string().min(1, 'Gate name is required'),
46
+ metric: z.string().min(1, 'Metric name is required'),
47
+ operator: z.enum(['gt', 'gte', 'lt', 'lte', 'eq', 'neq', 'between']),
48
+ threshold: z.number().or(z.array(z.number())),
49
+ severity: z.enum(['critical', 'major', 'minor']).default('major'),
50
+ action: z.enum(['block', 'warn', 'log']).default('block'),
51
+ })
52
+ .refine(
53
+ data => {
54
+ // POKA-YOKE: Operator-threshold mismatch guard (RPN 140 → 0)
55
+ if (data.operator === 'between') {
56
+ return Array.isArray(data.threshold) && data.threshold.length === 2;
57
+ }
58
+ return typeof data.threshold === 'number';
59
+ },
60
+ {
61
+ message:
62
+ "Operator 'between' requires threshold as [min, max] array; other operators require a single number. " +
63
+ "Example: { operator: 'between', threshold: [10, 90] } or { operator: 'gt', threshold: 50 }",
64
+ }
65
+ )
66
+ .refine(
67
+ data => {
68
+ // POKA-YOKE: Validate 'between' threshold order
69
+ if (data.operator === 'between' && Array.isArray(data.threshold)) {
70
+ return data.threshold[0] <= data.threshold[1];
71
+ }
72
+ return true;
73
+ },
74
+ {
75
+ message: "For 'between' operator, threshold[0] (min) must be <= threshold[1] (max)",
76
+ }
77
+ );
78
+
79
+ /**
80
+ * Statistical Process Control (SPC) data point
81
+ */
82
+ export const SPCDataPointSchema = z.object({
83
+ timestamp: z.date().or(z.string().transform(s => new Date(s))),
84
+ value: z.number(),
85
+ subgroup: z.string().optional(),
86
+ });
87
+
88
+ /**
89
+ * Quality Metrics Collector - Tracks Six Sigma metrics
90
+ *
91
+ * @class QualityMetricsCollector
92
+ */
93
+ export class QualityMetricsCollector {
94
+ /**
95
+ * Create a new quality metrics collector
96
+ *
97
+ * @param {object} options - Collector options
98
+ */
99
+ constructor(options = {}) {
100
+ /** @type {Map<string, number[]>} */
101
+ this.dataPoints = new Map();
102
+
103
+ /** @type {Map<string, QualityMetric>} */
104
+ this.metrics = new Map();
105
+
106
+ /** @type {Array<DefectRecord>} */
107
+ this.defects = [];
108
+
109
+ /** @type {number} */
110
+ this.maxDataPoints = options.maxDataPoints || 1000;
111
+
112
+ /** @type {Map<string, object>} */
113
+ this.qualityGates = new Map();
114
+
115
+ /** @type {Array<object>} */
116
+ this.auditLog = [];
117
+
118
+ /**
119
+ * POKA-YOKE: Maximum audit log size to prevent memory exhaustion (RPN 448 → 45)
120
+ * @type {number}
121
+ */
122
+ this.maxAuditLogSize = options.maxAuditLogSize || 10000;
123
+
124
+ /** @type {number} */
125
+ this.defectCount = 0;
126
+
127
+ /** @type {number} */
128
+ this.totalCount = 0;
129
+ }
130
+
131
+ /**
132
+ * Record a data point for a metric
133
+ *
134
+ * @param {string} metricName - Metric name
135
+ * @param {number} value - Measured value
136
+ * @param {object} context - Additional context
137
+ */
138
+ record(metricName, value, context = {}) {
139
+ if (!this.dataPoints.has(metricName)) {
140
+ this.dataPoints.set(metricName, []);
141
+ }
142
+
143
+ const points = this.dataPoints.get(metricName);
144
+ points.push(value);
145
+
146
+ // Maintain sliding window
147
+ if (points.length > this.maxDataPoints) {
148
+ points.shift();
149
+ }
150
+
151
+ this.totalCount++;
152
+
153
+ // Check control limits
154
+ this._checkControlLimits(metricName, value, context);
155
+ }
156
+
157
+ /**
158
+ * Record a defect
159
+ *
160
+ * @param {object} defect - Defect information
161
+ */
162
+ recordDefect(defect) {
163
+ const record = {
164
+ id: `DEF-${Date.now()}-${this.defects.length}`,
165
+ timestamp: new Date(),
166
+ ...defect,
167
+ };
168
+
169
+ this.defects.push(record);
170
+ this.defectCount++;
171
+
172
+ // Log to audit trail
173
+ this._auditLog('defect-recorded', record);
174
+ }
175
+
176
+ /**
177
+ * Register a quality gate
178
+ *
179
+ * @param {object} gate - Quality gate configuration
180
+ */
181
+ registerQualityGate(gate) {
182
+ const validated = QualityGateSchema.parse(gate);
183
+ this.qualityGates.set(validated.name, validated);
184
+ }
185
+
186
+ /**
187
+ * Check a value against quality gates
188
+ *
189
+ * @param {string} metricName - Metric to check
190
+ * @param {number} value - Value to check
191
+ * @returns {{passed: boolean, violations: object[]}} - Check result
192
+ */
193
+ checkQualityGates(metricName, value) {
194
+ const violations = [];
195
+
196
+ for (const gate of this.qualityGates.values()) {
197
+ if (gate.metric !== metricName) continue;
198
+
199
+ const passed = this._evaluateGate(gate, value);
200
+ if (!passed) {
201
+ violations.push({
202
+ gate: gate.name,
203
+ metric: metricName,
204
+ value,
205
+ threshold: gate.threshold,
206
+ operator: gate.operator,
207
+ severity: gate.severity,
208
+ action: gate.action,
209
+ });
210
+ }
211
+ }
212
+
213
+ return {
214
+ passed: violations.length === 0,
215
+ violations,
216
+ };
217
+ }
218
+
219
+ /**
220
+ * Calculate Statistical Process Control metrics
221
+ *
222
+ * @param {string} metricName - Metric name
223
+ * @returns {object} - SPC metrics (mean, stdDev, UCL, LCL, Cp, Cpk)
224
+ */
225
+ calculateSPC(metricName) {
226
+ const points = this.dataPoints.get(metricName) || [];
227
+ if (points.length < 2) {
228
+ return { error: 'Insufficient data points' };
229
+ }
230
+
231
+ // Calculate mean
232
+ const mean = points.reduce((sum, v) => sum + v, 0) / points.length;
233
+
234
+ // Calculate standard deviation
235
+ const variance =
236
+ points.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / (points.length - 1);
237
+ const stdDev = Math.sqrt(variance);
238
+
239
+ // Control limits (3-sigma)
240
+ const ucl = mean + 3 * stdDev;
241
+ const lcl = mean - 3 * stdDev;
242
+
243
+ // Process capability (requires spec limits)
244
+ const metric = this.metrics.get(metricName);
245
+ let cp = null;
246
+ let cpk = null;
247
+
248
+ if (metric && metric.ucl !== undefined && metric.lcl !== undefined) {
249
+ const usl = metric.ucl;
250
+ const lsl = metric.lcl;
251
+
252
+ cp = (usl - lsl) / (6 * stdDev);
253
+ cpk = Math.min((usl - mean) / (3 * stdDev), (mean - lsl) / (3 * stdDev));
254
+ }
255
+
256
+ return {
257
+ mean,
258
+ stdDev,
259
+ ucl,
260
+ lcl,
261
+ cp,
262
+ cpk,
263
+ n: points.length,
264
+ min: Math.min(...points),
265
+ max: Math.max(...points),
266
+ };
267
+ }
268
+
269
+ /**
270
+ * Detect statistical outliers (defects)
271
+ *
272
+ * @param {string} metricName - Metric name
273
+ * @param {number} sigmaLevel - Sigma level for detection (default: 3)
274
+ * @returns {number[]} - Indices of outlier data points
275
+ */
276
+ detectOutliers(metricName, sigmaLevel = 3) {
277
+ const spc = this.calculateSPC(metricName);
278
+ if (spc.error) return [];
279
+
280
+ const points = this.dataPoints.get(metricName) || [];
281
+ const threshold = sigmaLevel * spc.stdDev;
282
+
283
+ return points
284
+ .map((v, i) => ({ value: v, index: i }))
285
+ .filter(({ value }) => Math.abs(value - spc.mean) > threshold)
286
+ .map(({ index }) => index);
287
+ }
288
+
289
+ /**
290
+ * Calculate Defects Per Million Opportunities (DPMO)
291
+ *
292
+ * @returns {number} - DPMO value
293
+ */
294
+ calculateDPMO() {
295
+ if (this.totalCount === 0) return 0;
296
+ return (this.defectCount / this.totalCount) * 1000000;
297
+ }
298
+
299
+ /**
300
+ * Calculate Sigma Level
301
+ *
302
+ * @returns {number} - Sigma level (higher is better)
303
+ */
304
+ calculateSigmaLevel() {
305
+ const dpmo = this.calculateDPMO();
306
+ if (dpmo === 0) return 6; // Perfect
307
+
308
+ // Approximate sigma level from DPMO
309
+ // Using simplified lookup table
310
+ if (dpmo <= 3.4) return 6;
311
+ if (dpmo <= 233) return 5;
312
+ if (dpmo <= 6210) return 4;
313
+ if (dpmo <= 66807) return 3;
314
+ if (dpmo <= 308538) return 2;
315
+ if (dpmo <= 691462) return 1;
316
+ return 0;
317
+ }
318
+
319
+ /**
320
+ * Generate 5 Whys root cause analysis template
321
+ *
322
+ * @param {DefectRecord} defect - Defect to analyze
323
+ * @returns {object} - 5 Whys template
324
+ */
325
+ generateRootCauseTemplate(defect) {
326
+ return {
327
+ defectId: defect.id,
328
+ problem: defect.type,
329
+ why1: { question: 'Why did this happen?', answer: null },
330
+ why2: { question: 'Why did that cause this?', answer: null },
331
+ why3: { question: 'Why was that the case?', answer: null },
332
+ why4: { question: 'Why did that occur?', answer: null },
333
+ why5: { question: 'What is the root cause?', answer: null },
334
+ rootCause: null,
335
+ preventiveAction: null,
336
+ timestamp: new Date(),
337
+ };
338
+ }
339
+
340
+ /**
341
+ * Register a Kaizen improvement event
342
+ *
343
+ * @param {object} event - Kaizen event details
344
+ */
345
+ registerKaizenEvent(event) {
346
+ const record = {
347
+ id: `KAIZEN-${Date.now()}`,
348
+ type: 'kaizen-event',
349
+ timestamp: new Date(),
350
+ status: 'open',
351
+ ...event,
352
+ };
353
+
354
+ this._auditLog('kaizen-registered', record);
355
+ return record;
356
+ }
357
+
358
+ /**
359
+ * Get quality summary report
360
+ *
361
+ * @returns {object} - Quality summary
362
+ */
363
+ getSummary() {
364
+ return {
365
+ totalMeasurements: this.totalCount,
366
+ totalDefects: this.defectCount,
367
+ dpmo: this.calculateDPMO(),
368
+ sigmaLevel: this.calculateSigmaLevel(),
369
+ metricsTracked: this.metrics.size,
370
+ dataPointsStored: Array.from(this.dataPoints.values()).reduce(
371
+ (sum, arr) => sum + arr.length,
372
+ 0
373
+ ),
374
+ qualityGatesRegistered: this.qualityGates.size,
375
+ auditLogEntries: this.auditLog.length,
376
+ recentDefects: this.defects.slice(-10),
377
+ };
378
+ }
379
+
380
+ /**
381
+ * Export audit trail
382
+ *
383
+ * @param {object} options - Export options
384
+ * @returns {Array} - Audit trail entries
385
+ */
386
+ exportAuditTrail(options = {}) {
387
+ let entries = [...this.auditLog];
388
+
389
+ if (options.startTime) {
390
+ entries = entries.filter(e => e.timestamp >= options.startTime);
391
+ }
392
+ if (options.endTime) {
393
+ entries = entries.filter(e => e.timestamp <= options.endTime);
394
+ }
395
+ if (options.type) {
396
+ entries = entries.filter(e => e.type === options.type);
397
+ }
398
+
399
+ return entries;
400
+ }
401
+
402
+ /**
403
+ * Check control limits and record violations
404
+ * @private
405
+ */
406
+ _checkControlLimits(metricName, value, context) {
407
+ const spc = this.calculateSPC(metricName);
408
+ if (spc.error) return;
409
+
410
+ if (value > spc.ucl || value < spc.lcl) {
411
+ this.recordDefect({
412
+ type: 'control-limit-violation',
413
+ source: metricName,
414
+ severity: 'major',
415
+ context: {
416
+ value,
417
+ ucl: spc.ucl,
418
+ lcl: spc.lcl,
419
+ mean: spc.mean,
420
+ ...context,
421
+ },
422
+ });
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Evaluate a quality gate
428
+ * @private
429
+ */
430
+ _evaluateGate(gate, value) {
431
+ switch (gate.operator) {
432
+ case 'gt':
433
+ return value > gate.threshold;
434
+ case 'gte':
435
+ return value >= gate.threshold;
436
+ case 'lt':
437
+ return value < gate.threshold;
438
+ case 'lte':
439
+ return value <= gate.threshold;
440
+ case 'eq':
441
+ return value === gate.threshold;
442
+ case 'neq':
443
+ return value !== gate.threshold;
444
+ case 'between':
445
+ return (
446
+ Array.isArray(gate.threshold) && value >= gate.threshold[0] && value <= gate.threshold[1]
447
+ );
448
+ default:
449
+ return true;
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Add entry to audit log
455
+ * POKA-YOKE: Includes size limit to prevent memory exhaustion (RPN 448 → 45)
456
+ * @private
457
+ */
458
+ _auditLog(type, data) {
459
+ // POKA-YOKE: FIFO eviction to prevent unbounded memory growth
460
+ if (this.auditLog.length >= this.maxAuditLogSize) {
461
+ // Remove oldest entries (10% of max to reduce frequent shifts)
462
+ const removeCount = Math.max(1, Math.floor(this.maxAuditLogSize * 0.1));
463
+ this.auditLog.splice(0, removeCount);
464
+
465
+ // Log warning once when eviction starts
466
+ if (!this._auditEvictionWarned) {
467
+ console.warn(
468
+ `[POKA-YOKE] Audit log reached max size (${this.maxAuditLogSize}). ` +
469
+ `Oldest entries will be evicted. Increase maxAuditLogSize if needed.`
470
+ );
471
+ this._auditEvictionWarned = true;
472
+ }
473
+ }
474
+
475
+ this.auditLog.push({
476
+ type,
477
+ timestamp: new Date(),
478
+ data,
479
+ });
480
+ }
481
+ }
482
+
483
+ /**
484
+ * Create hook validators for quality metrics
485
+ *
486
+ * @param {QualityMetricsCollector} collector - Metrics collector
487
+ * @returns {object} - Hook factory functions
488
+ */
489
+ export function createQualityHooks(collector) {
490
+ return {
491
+ /**
492
+ * Create a quality gate validation hook
493
+ */
494
+ createQualityGateHook: (gateName, metricExtractor) => ({
495
+ name: `quality-gate-${gateName}`,
496
+ trigger: 'quality-gate',
497
+ validate: data => {
498
+ const value = metricExtractor(data);
499
+ const result = collector.checkQualityGates(gateName, value);
500
+ if (!result.passed) {
501
+ const criticalViolations = result.violations.filter(v => v.action === 'block');
502
+ return criticalViolations.length === 0;
503
+ }
504
+ return true;
505
+ },
506
+ metadata: { gateName },
507
+ }),
508
+
509
+ /**
510
+ * Create a defect detection hook
511
+ */
512
+ createDefectDetectionHook: (metricName, extractor) => ({
513
+ name: `defect-detection-${metricName}`,
514
+ trigger: 'defect-detection',
515
+ validate: data => {
516
+ const value = extractor(data);
517
+ collector.record(metricName, value);
518
+ const outliers = collector.detectOutliers(metricName);
519
+ return outliers.length === 0;
520
+ },
521
+ metadata: { metricName },
522
+ }),
523
+
524
+ /**
525
+ * Create an audit trail hook
526
+ */
527
+ createAuditTrailHook: operationType => ({
528
+ name: `audit-trail-${operationType}`,
529
+ trigger: 'audit-trail',
530
+ transform: data => {
531
+ collector._auditLog(operationType, { data, timestamp: new Date() });
532
+ return data;
533
+ },
534
+ metadata: { operationType },
535
+ }),
536
+ };
537
+ }
538
+
539
+ export default {
540
+ QualityMetricsCollector,
541
+ createQualityHooks,
542
+ QualityGateSchema,
543
+ SPCDataPointSchema,
544
+ };