@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.
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/package.json +70 -0
- package/src/hooks/builtin-hooks.mjs +296 -0
- package/src/hooks/condition-cache.mjs +109 -0
- package/src/hooks/condition-evaluator.mjs +722 -0
- package/src/hooks/define-hook.mjs +211 -0
- package/src/hooks/effect-sandbox-worker.mjs +170 -0
- package/src/hooks/effect-sandbox.mjs +517 -0
- package/src/hooks/file-resolver.mjs +387 -0
- package/src/hooks/hook-chain-compiler.mjs +236 -0
- package/src/hooks/hook-executor-batching.mjs +277 -0
- package/src/hooks/hook-executor.mjs +465 -0
- package/src/hooks/hook-management.mjs +202 -0
- package/src/hooks/hook-scheduler.mjs +413 -0
- package/src/hooks/knowledge-hook-engine.mjs +358 -0
- package/src/hooks/knowledge-hook-manager.mjs +269 -0
- package/src/hooks/observability.mjs +531 -0
- package/src/hooks/policy-pack.mjs +572 -0
- package/src/hooks/quad-pool.mjs +249 -0
- package/src/hooks/quality-metrics.mjs +544 -0
- package/src/hooks/security/error-sanitizer.mjs +257 -0
- package/src/hooks/security/path-validator.mjs +194 -0
- package/src/hooks/security/sandbox-restrictions.mjs +331 -0
- package/src/hooks/telemetry.mjs +167 -0
- package/src/index.mjs +101 -0
- package/src/security/sandbox/browser-executor.mjs +220 -0
- package/src/security/sandbox/detector.mjs +342 -0
- package/src/security/sandbox/isolated-vm-executor.mjs +373 -0
- package/src/security/sandbox/vm2-executor.mjs +217 -0
- package/src/security/sandbox/worker-executor-runtime.mjs +74 -0
- package/src/security/sandbox/worker-executor.mjs +212 -0
- 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
|
+
};
|