@unrdf/knowledge-engine 5.0.1 → 26.4.3
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/package.json +13 -7
- package/src/ai-enhanced-search.mjs +371 -0
- package/src/anomaly-detector.mjs +226 -0
- package/src/artifact-generator.mjs +251 -0
- package/src/browser.mjs +1 -1
- package/src/chatman/disruption-arithmetic.mjs +140 -0
- package/src/chatman/market-dynamics.mjs +140 -0
- package/src/chatman/organizational-dynamics.mjs +140 -0
- package/src/chatman/strategic-dynamics.mjs +140 -0
- package/src/chatman-config-loader.mjs +282 -0
- package/src/chatman-engine.mjs +431 -0
- package/src/chatman-operator.mjs +342 -0
- package/src/dark-field-detector.mjs +312 -0
- package/src/formation-theorems.mjs +345 -0
- package/src/index.mjs +20 -2
- package/src/knowledge-hook-manager.mjs +1 -1
- package/src/lockchain-writer-browser.mjs +2 -2
- package/src/observability.mjs +40 -4
- package/src/query-optimizer.mjs +1 -1
- package/src/resolution-layer.mjs +1 -1
- package/src/transaction.mjs +11 -9
- package/README.md +0 -84
- package/src/browser-shims.mjs +0 -343
- package/src/canonicalize.mjs +0 -414
- package/src/condition-cache.mjs +0 -109
- package/src/condition-evaluator.mjs +0 -722
- package/src/dark-matter-core.mjs +0 -742
- package/src/define-hook.mjs +0 -213
- package/src/effect-sandbox-browser.mjs +0 -283
- package/src/effect-sandbox-worker.mjs +0 -170
- package/src/effect-sandbox.mjs +0 -517
- package/src/engines/index.mjs +0 -11
- package/src/engines/rdf-engine.mjs +0 -299
- package/src/file-resolver.mjs +0 -387
- package/src/hook-executor-batching.mjs +0 -277
- package/src/hook-executor.mjs +0 -870
- package/src/hook-management.mjs +0 -150
- package/src/ken-parliment.mjs +0 -119
- package/src/ken.mjs +0 -149
- package/src/knowledge-engine/builtin-rules.mjs +0 -190
- package/src/knowledge-engine/inference-engine.mjs +0 -418
- package/src/knowledge-engine/knowledge-engine.mjs +0 -317
- package/src/knowledge-engine/pattern-dsl.mjs +0 -142
- package/src/knowledge-engine/pattern-matcher.mjs +0 -215
- package/src/knowledge-engine/rules.mjs +0 -184
- package/src/knowledge-engine.mjs +0 -319
- package/src/knowledge-hook-engine.mjs +0 -360
- package/src/knowledge-substrate-core.mjs +0 -927
- package/src/lite.mjs +0 -222
- package/src/lockchain-writer.mjs +0 -602
- package/src/monitoring/andon-signals.mjs +0 -775
- package/src/parse.mjs +0 -290
- package/src/performance-optimizer.mjs +0 -678
- package/src/policy-pack.mjs +0 -572
- package/src/query-cache.mjs +0 -116
- package/src/query.mjs +0 -306
- package/src/reason.mjs +0 -350
- package/src/schemas.mjs +0 -1063
- package/src/security/error-sanitizer.mjs +0 -257
- package/src/security/path-validator.mjs +0 -194
- package/src/security/sandbox-restrictions.mjs +0 -331
- package/src/security-validator.mjs +0 -389
- package/src/store-cache.mjs +0 -137
- package/src/telemetry.mjs +0 -167
- package/src/utils/adaptive-monitor.mjs +0 -746
- package/src/utils/circuit-breaker.mjs +0 -513
- package/src/utils/edge-case-handler.mjs +0 -503
- package/src/utils/memory-manager.mjs +0 -498
- package/src/utils/ring-buffer.mjs +0 -282
- package/src/validate.mjs +0 -319
- package/src/validators/index.mjs +0 -338
|
@@ -1,775 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file Andon signal system for production monitoring
|
|
3
|
-
* @module knowledge-engine/monitoring/andon-signals
|
|
4
|
-
*
|
|
5
|
-
* @description
|
|
6
|
-
* Provides RED/YELLOW/GREEN status for all critical systems.
|
|
7
|
-
* Implements the Andon cord pattern from Toyota Production System
|
|
8
|
-
* for immediate visibility into system health.
|
|
9
|
-
*
|
|
10
|
-
* Andon States:
|
|
11
|
-
* - GREEN: Score >= 80, system healthy, all signals nominal
|
|
12
|
-
* - YELLOW: Score 60-79, warning state, investigation needed
|
|
13
|
-
* - RED: Score < 60, critical state, immediate action required
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { z } from 'zod';
|
|
17
|
-
import { EventEmitter } from 'events';
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Andon signal states
|
|
21
|
-
* @readonly
|
|
22
|
-
* @enum {string}
|
|
23
|
-
*/
|
|
24
|
-
export const AndonState = {
|
|
25
|
-
GREEN: 'green',
|
|
26
|
-
YELLOW: 'yellow',
|
|
27
|
-
RED: 'red',
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Signal type categories
|
|
32
|
-
* @readonly
|
|
33
|
-
* @enum {string}
|
|
34
|
-
*/
|
|
35
|
-
export const SignalCategory = {
|
|
36
|
-
VALIDATION: 'validation',
|
|
37
|
-
CI_CD: 'ci_cd',
|
|
38
|
-
PERFORMANCE: 'performance',
|
|
39
|
-
SECURITY: 'security',
|
|
40
|
-
HEALTH: 'health',
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Schema for signal configuration
|
|
45
|
-
*/
|
|
46
|
-
const SignalConfigSchema = z.object({
|
|
47
|
-
name: z.string().min(1),
|
|
48
|
-
category: z.nativeEnum(SignalCategory),
|
|
49
|
-
description: z.string().optional(),
|
|
50
|
-
weight: z.number().min(0).max(1).default(1),
|
|
51
|
-
thresholds: z
|
|
52
|
-
.object({
|
|
53
|
-
green: z.number().min(0).max(100).default(80),
|
|
54
|
-
yellow: z.number().min(0).max(100).default(60),
|
|
55
|
-
})
|
|
56
|
-
.default({ green: 80, yellow: 60 }),
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Schema for signal state
|
|
61
|
-
*/
|
|
62
|
-
const SignalStateSchema = z.object({
|
|
63
|
-
name: z.string(),
|
|
64
|
-
category: z.nativeEnum(SignalCategory),
|
|
65
|
-
state: z.nativeEnum(AndonState),
|
|
66
|
-
score: z.number().min(0).max(100),
|
|
67
|
-
message: z.string().optional(),
|
|
68
|
-
timestamp: z.number(),
|
|
69
|
-
metadata: z.record(z.unknown()).optional(),
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Compute Andon signal state from a score
|
|
74
|
-
* @param {number} score - Score value (0-100)
|
|
75
|
-
* @param {Object} [thresholds] - Custom thresholds
|
|
76
|
-
* @param {number} [thresholds.green=80] - Green threshold
|
|
77
|
-
* @param {number} [thresholds.yellow=60] - Yellow threshold
|
|
78
|
-
* @returns {AndonState} Computed signal state
|
|
79
|
-
*/
|
|
80
|
-
export function computeSignalState(score, thresholds = { green: 80, yellow: 60 }) {
|
|
81
|
-
const normalizedScore = Math.max(0, Math.min(100, score));
|
|
82
|
-
|
|
83
|
-
if (normalizedScore >= thresholds.green) {
|
|
84
|
-
return AndonState.GREEN;
|
|
85
|
-
}
|
|
86
|
-
if (normalizedScore >= thresholds.yellow) {
|
|
87
|
-
return AndonState.YELLOW;
|
|
88
|
-
}
|
|
89
|
-
return AndonState.RED;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Get state priority (higher = more critical)
|
|
94
|
-
* @param {AndonState} state - Signal state
|
|
95
|
-
* @returns {number} Priority value
|
|
96
|
-
*/
|
|
97
|
-
export function getStatePriority(state) {
|
|
98
|
-
switch (state) {
|
|
99
|
-
case AndonState.RED:
|
|
100
|
-
return 3;
|
|
101
|
-
case AndonState.YELLOW:
|
|
102
|
-
return 2;
|
|
103
|
-
case AndonState.GREEN:
|
|
104
|
-
return 1;
|
|
105
|
-
default:
|
|
106
|
-
return 0;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Compare two states and return the more critical one
|
|
112
|
-
* @param {AndonState} state1 - First state
|
|
113
|
-
* @param {AndonState} state2 - Second state
|
|
114
|
-
* @returns {AndonState} More critical state
|
|
115
|
-
*/
|
|
116
|
-
export function getWorstState(state1, state2) {
|
|
117
|
-
return getStatePriority(state1) > getStatePriority(state2) ? state1 : state2;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Andon Signal Manager
|
|
122
|
-
* Manages all system signals and provides deployment gates
|
|
123
|
-
*/
|
|
124
|
-
export class AndonSignalManager extends EventEmitter {
|
|
125
|
-
/**
|
|
126
|
-
* Create an Andon Signal Manager
|
|
127
|
-
* @param {Object} [config] - Configuration options
|
|
128
|
-
* @param {Object} [config.thresholds] - Default thresholds
|
|
129
|
-
* @param {number} [config.thresholds.green=80] - Green threshold
|
|
130
|
-
* @param {number} [config.thresholds.yellow=60] - Yellow threshold
|
|
131
|
-
* @param {boolean} [config.strictDeployment=true] - Require all GREEN for deployment
|
|
132
|
-
* @param {string[]} [config.requiredSignals=[]] - Signals required for deployment
|
|
133
|
-
*/
|
|
134
|
-
constructor(config = {}) {
|
|
135
|
-
super();
|
|
136
|
-
|
|
137
|
-
this.config = {
|
|
138
|
-
thresholds: config.thresholds || { green: 80, yellow: 60 },
|
|
139
|
-
strictDeployment: config.strictDeployment !== false,
|
|
140
|
-
requiredSignals: config.requiredSignals || [],
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
/** @type {Map<string, Object>} */
|
|
144
|
-
this.signals = new Map();
|
|
145
|
-
|
|
146
|
-
/** @type {Map<string, Object>} */
|
|
147
|
-
this.signalConfigs = new Map();
|
|
148
|
-
|
|
149
|
-
/** @type {Array<Function>} */
|
|
150
|
-
this.listeners = [];
|
|
151
|
-
|
|
152
|
-
/** @type {Map<string, Object>} */
|
|
153
|
-
this.history = new Map();
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Register a signal configuration
|
|
158
|
-
* @param {Object} config - Signal configuration
|
|
159
|
-
* @returns {AndonSignalManager} This instance for chaining
|
|
160
|
-
*/
|
|
161
|
-
registerSignal(config) {
|
|
162
|
-
const validated = SignalConfigSchema.parse(config);
|
|
163
|
-
this.signalConfigs.set(validated.name, validated);
|
|
164
|
-
return this;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Update a signal's state
|
|
169
|
-
* @param {string} name - Signal name
|
|
170
|
-
* @param {number} score - Score value (0-100)
|
|
171
|
-
* @param {Object} [options] - Additional options
|
|
172
|
-
* @param {string} [options.message] - Status message
|
|
173
|
-
* @param {Object} [options.metadata] - Additional metadata
|
|
174
|
-
* @returns {Object} Updated signal state
|
|
175
|
-
*/
|
|
176
|
-
updateSignal(name, score, options = {}) {
|
|
177
|
-
const config = this.signalConfigs.get(name);
|
|
178
|
-
if (!config) {
|
|
179
|
-
throw new Error(`Signal '${name}' not registered. Call registerSignal first.`);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const thresholds = config.thresholds || this.config.thresholds;
|
|
183
|
-
const state = computeSignalState(score, thresholds);
|
|
184
|
-
|
|
185
|
-
const signalState = SignalStateSchema.parse({
|
|
186
|
-
name,
|
|
187
|
-
category: config.category,
|
|
188
|
-
state,
|
|
189
|
-
score,
|
|
190
|
-
message: options.message,
|
|
191
|
-
timestamp: Date.now(),
|
|
192
|
-
metadata: options.metadata,
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const previousState = this.signals.get(name);
|
|
196
|
-
this.signals.set(name, signalState);
|
|
197
|
-
|
|
198
|
-
// Track history
|
|
199
|
-
if (!this.history.has(name)) {
|
|
200
|
-
this.history.set(name, []);
|
|
201
|
-
}
|
|
202
|
-
const historyEntry = this.history.get(name);
|
|
203
|
-
historyEntry.push(signalState);
|
|
204
|
-
// Keep last 100 entries
|
|
205
|
-
if (historyEntry.length > 100) {
|
|
206
|
-
historyEntry.shift();
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Emit change event if state changed
|
|
210
|
-
if (!previousState || previousState.state !== state) {
|
|
211
|
-
this.emit('signalChange', {
|
|
212
|
-
signal: signalState,
|
|
213
|
-
previousState: previousState?.state,
|
|
214
|
-
newState: state,
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// Notify registered listeners
|
|
218
|
-
for (const listener of this.listeners) {
|
|
219
|
-
try {
|
|
220
|
-
listener({
|
|
221
|
-
signal: signalState,
|
|
222
|
-
previousState: previousState?.state,
|
|
223
|
-
newState: state,
|
|
224
|
-
});
|
|
225
|
-
} catch (err) {
|
|
226
|
-
console.error(`Listener error: ${err.message}`);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return signalState;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Register validation signals from OTEL validator
|
|
236
|
-
* @param {Object} otelValidator - OTEL validation runner or results
|
|
237
|
-
* @returns {AndonSignalManager} This instance for chaining
|
|
238
|
-
*/
|
|
239
|
-
registerValidationSignals(otelValidator) {
|
|
240
|
-
// Default validation signals (7 core features)
|
|
241
|
-
const validationSignals = [
|
|
242
|
-
{
|
|
243
|
-
name: 'knowledge-engine-core',
|
|
244
|
-
category: SignalCategory.VALIDATION,
|
|
245
|
-
weight: 0.3,
|
|
246
|
-
description: 'Core knowledge engine operations',
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
name: 'knowledge-hooks-api',
|
|
250
|
-
category: SignalCategory.VALIDATION,
|
|
251
|
-
weight: 0.2,
|
|
252
|
-
description: 'Knowledge hooks API',
|
|
253
|
-
},
|
|
254
|
-
{
|
|
255
|
-
name: 'policy-packs',
|
|
256
|
-
category: SignalCategory.VALIDATION,
|
|
257
|
-
weight: 0.15,
|
|
258
|
-
description: 'Policy pack system',
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: 'lockchain-integrity',
|
|
262
|
-
category: SignalCategory.VALIDATION,
|
|
263
|
-
weight: 0.15,
|
|
264
|
-
description: 'Cryptographic audit trail',
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
name: 'transaction-manager',
|
|
268
|
-
category: SignalCategory.VALIDATION,
|
|
269
|
-
weight: 0.1,
|
|
270
|
-
description: 'ACID transaction guarantees',
|
|
271
|
-
},
|
|
272
|
-
{
|
|
273
|
-
name: 'browser-compatibility',
|
|
274
|
-
category: SignalCategory.VALIDATION,
|
|
275
|
-
weight: 0.1,
|
|
276
|
-
description: 'Browser compatibility layer',
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
name: 'isolated-vm-security',
|
|
280
|
-
category: SignalCategory.VALIDATION,
|
|
281
|
-
weight: 0.05,
|
|
282
|
-
description: 'Sandbox security',
|
|
283
|
-
},
|
|
284
|
-
];
|
|
285
|
-
|
|
286
|
-
for (const signal of validationSignals) {
|
|
287
|
-
this.registerSignal(signal);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// If otelValidator has results, update signals
|
|
291
|
-
if (otelValidator?.features) {
|
|
292
|
-
for (const feature of otelValidator.features) {
|
|
293
|
-
if (this.signalConfigs.has(feature.name)) {
|
|
294
|
-
this.updateSignal(feature.name, feature.score, {
|
|
295
|
-
message: feature.passed
|
|
296
|
-
? 'Validation passed'
|
|
297
|
-
: `${feature.violations?.length || 0} violations`,
|
|
298
|
-
metadata: {
|
|
299
|
-
passed: feature.passed,
|
|
300
|
-
violations: feature.violations,
|
|
301
|
-
metrics: feature.metrics,
|
|
302
|
-
},
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return this;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Register CI/CD signals from GitHub Actions or similar
|
|
313
|
-
* @param {Object} config - CI/CD configuration
|
|
314
|
-
* @param {string} [config.workflowUrl] - GitHub Actions workflow URL
|
|
315
|
-
* @param {Object[]} [config.jobs] - Job results
|
|
316
|
-
* @returns {AndonSignalManager} This instance for chaining
|
|
317
|
-
*/
|
|
318
|
-
registerCISignals(config = {}) {
|
|
319
|
-
// Default CI/CD signals (9 pipeline stages)
|
|
320
|
-
const ciSignals = [
|
|
321
|
-
{
|
|
322
|
-
name: 'ci-lint',
|
|
323
|
-
category: SignalCategory.CI_CD,
|
|
324
|
-
weight: 0.1,
|
|
325
|
-
description: 'Code linting',
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
name: 'ci-typecheck',
|
|
329
|
-
category: SignalCategory.CI_CD,
|
|
330
|
-
weight: 0.1,
|
|
331
|
-
description: 'Type checking',
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
name: 'ci-unit-tests',
|
|
335
|
-
category: SignalCategory.CI_CD,
|
|
336
|
-
weight: 0.2,
|
|
337
|
-
description: 'Unit test suite',
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
name: 'ci-integration-tests',
|
|
341
|
-
category: SignalCategory.CI_CD,
|
|
342
|
-
weight: 0.15,
|
|
343
|
-
description: 'Integration tests',
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
name: 'ci-e2e-tests',
|
|
347
|
-
category: SignalCategory.CI_CD,
|
|
348
|
-
weight: 0.15,
|
|
349
|
-
description: 'End-to-end tests',
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
name: 'ci-security-scan',
|
|
353
|
-
category: SignalCategory.CI_CD,
|
|
354
|
-
weight: 0.1,
|
|
355
|
-
description: 'Security scanning',
|
|
356
|
-
},
|
|
357
|
-
{
|
|
358
|
-
name: 'ci-build',
|
|
359
|
-
category: SignalCategory.CI_CD,
|
|
360
|
-
weight: 0.1,
|
|
361
|
-
description: 'Build process',
|
|
362
|
-
},
|
|
363
|
-
{
|
|
364
|
-
name: 'ci-coverage',
|
|
365
|
-
category: SignalCategory.CI_CD,
|
|
366
|
-
weight: 0.05,
|
|
367
|
-
description: 'Code coverage',
|
|
368
|
-
},
|
|
369
|
-
{
|
|
370
|
-
name: 'ci-performance',
|
|
371
|
-
category: SignalCategory.CI_CD,
|
|
372
|
-
weight: 0.05,
|
|
373
|
-
description: 'Performance benchmarks',
|
|
374
|
-
},
|
|
375
|
-
];
|
|
376
|
-
|
|
377
|
-
for (const signal of ciSignals) {
|
|
378
|
-
this.registerSignal(signal);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Parse job results if provided
|
|
382
|
-
if (config.jobs && Array.isArray(config.jobs)) {
|
|
383
|
-
for (const job of config.jobs) {
|
|
384
|
-
const signalName = this.mapJobToSignal(job.name || job.id);
|
|
385
|
-
if (signalName && this.signalConfigs.has(signalName)) {
|
|
386
|
-
const score = this.computeJobScore(job);
|
|
387
|
-
this.updateSignal(signalName, score, {
|
|
388
|
-
message: job.conclusion || job.status,
|
|
389
|
-
metadata: {
|
|
390
|
-
jobId: job.id,
|
|
391
|
-
status: job.status,
|
|
392
|
-
conclusion: job.conclusion,
|
|
393
|
-
duration: job.duration,
|
|
394
|
-
url: job.url,
|
|
395
|
-
},
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return this;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Map GitHub Actions job name to signal name
|
|
406
|
-
* @param {string} jobName - Job name
|
|
407
|
-
* @returns {string|null} Signal name or null
|
|
408
|
-
* @private
|
|
409
|
-
*/
|
|
410
|
-
mapJobToSignal(jobName) {
|
|
411
|
-
// Normalize: lowercase and replace hyphens/underscores with nothing for matching
|
|
412
|
-
const jobNameLower = (jobName || '').toLowerCase();
|
|
413
|
-
const jobNameNormalized = jobNameLower.replace(/[-_\s]/g, '');
|
|
414
|
-
|
|
415
|
-
// Order matters: more specific patterns first
|
|
416
|
-
const mappings = [
|
|
417
|
-
{ patterns: ['typecheck'], signal: 'ci-typecheck' },
|
|
418
|
-
{ patterns: ['unittest'], signal: 'ci-unit-tests' },
|
|
419
|
-
{ patterns: ['integration'], signal: 'ci-integration-tests' },
|
|
420
|
-
{ patterns: ['e2e', 'endtoend'], signal: 'ci-e2e-tests' },
|
|
421
|
-
{ patterns: ['security'], signal: 'ci-security-scan' },
|
|
422
|
-
{
|
|
423
|
-
patterns: ['performance', 'benchmark', 'perf'],
|
|
424
|
-
signal: 'ci-performance',
|
|
425
|
-
},
|
|
426
|
-
{ patterns: ['coverage'], signal: 'ci-coverage' },
|
|
427
|
-
{ patterns: ['build'], signal: 'ci-build' },
|
|
428
|
-
{ patterns: ['lint'], signal: 'ci-lint' },
|
|
429
|
-
{ patterns: ['test'], signal: 'ci-unit-tests' },
|
|
430
|
-
{ patterns: ['type'], signal: 'ci-typecheck' },
|
|
431
|
-
];
|
|
432
|
-
|
|
433
|
-
for (const { patterns, signal } of mappings) {
|
|
434
|
-
for (const pattern of patterns) {
|
|
435
|
-
if (jobNameNormalized.includes(pattern) || jobNameLower.includes(pattern)) {
|
|
436
|
-
return signal;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
return null;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Compute score from job result
|
|
446
|
-
* @param {Object} job - Job result
|
|
447
|
-
* @returns {number} Score (0-100)
|
|
448
|
-
* @private
|
|
449
|
-
*/
|
|
450
|
-
computeJobScore(job) {
|
|
451
|
-
if (job.score !== undefined) {
|
|
452
|
-
return job.score;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const conclusion = (job.conclusion || '').toLowerCase();
|
|
456
|
-
const status = (job.status || '').toLowerCase();
|
|
457
|
-
|
|
458
|
-
if (conclusion === 'success' || status === 'completed') {
|
|
459
|
-
return 100;
|
|
460
|
-
}
|
|
461
|
-
if (conclusion === 'failure' || status === 'failed') {
|
|
462
|
-
return 0;
|
|
463
|
-
}
|
|
464
|
-
if (conclusion === 'cancelled' || status === 'cancelled') {
|
|
465
|
-
return 30;
|
|
466
|
-
}
|
|
467
|
-
if (conclusion === 'skipped') {
|
|
468
|
-
return 50;
|
|
469
|
-
}
|
|
470
|
-
if (status === 'in_progress' || status === 'queued') {
|
|
471
|
-
return 70;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return 50; // Unknown state
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Register performance signals
|
|
479
|
-
* @param {Object} [performanceMetrics] - Performance metrics from ObservabilityManager
|
|
480
|
-
* @returns {AndonSignalManager} This instance for chaining
|
|
481
|
-
*/
|
|
482
|
-
registerPerformanceSignals(performanceMetrics = null) {
|
|
483
|
-
const perfSignals = [
|
|
484
|
-
{
|
|
485
|
-
name: 'perf-latency',
|
|
486
|
-
category: SignalCategory.PERFORMANCE,
|
|
487
|
-
weight: 0.3,
|
|
488
|
-
description: 'Response latency P95',
|
|
489
|
-
},
|
|
490
|
-
{
|
|
491
|
-
name: 'perf-throughput',
|
|
492
|
-
category: SignalCategory.PERFORMANCE,
|
|
493
|
-
weight: 0.25,
|
|
494
|
-
description: 'Transaction throughput',
|
|
495
|
-
},
|
|
496
|
-
{
|
|
497
|
-
name: 'perf-error-rate',
|
|
498
|
-
category: SignalCategory.PERFORMANCE,
|
|
499
|
-
weight: 0.25,
|
|
500
|
-
description: 'Error rate',
|
|
501
|
-
},
|
|
502
|
-
{
|
|
503
|
-
name: 'perf-memory',
|
|
504
|
-
category: SignalCategory.PERFORMANCE,
|
|
505
|
-
weight: 0.2,
|
|
506
|
-
description: 'Memory usage',
|
|
507
|
-
},
|
|
508
|
-
];
|
|
509
|
-
|
|
510
|
-
for (const signal of perfSignals) {
|
|
511
|
-
this.registerSignal(signal);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
if (performanceMetrics) {
|
|
515
|
-
this.updatePerformanceSignals(performanceMetrics);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
return this;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Update performance signals from metrics
|
|
523
|
-
* @param {Object} metrics - Performance metrics
|
|
524
|
-
*/
|
|
525
|
-
updatePerformanceSignals(metrics) {
|
|
526
|
-
// Latency score: 100ms = 100, 500ms = 80, 1000ms = 60, 2000ms+ = 0
|
|
527
|
-
if (metrics.transactionLatency) {
|
|
528
|
-
const p95 = metrics.transactionLatency.p95 || 0;
|
|
529
|
-
const latencyScore = Math.max(0, Math.min(100, 100 - p95 / 20));
|
|
530
|
-
this.updateSignal('perf-latency', latencyScore, {
|
|
531
|
-
message: `P95: ${p95}ms`,
|
|
532
|
-
metadata: metrics.transactionLatency,
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Error rate score: 0% = 100, 1% = 80, 5% = 60, 10%+ = 0
|
|
537
|
-
if (metrics.errorRate !== undefined) {
|
|
538
|
-
const errorPct = metrics.errorRate * 100;
|
|
539
|
-
const errorScore = Math.max(0, Math.min(100, 100 - errorPct * 10));
|
|
540
|
-
this.updateSignal('perf-error-rate', errorScore, {
|
|
541
|
-
message: `Error rate: ${errorPct.toFixed(2)}%`,
|
|
542
|
-
metadata: { errorRate: metrics.errorRate },
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// Throughput score: relative to expected baseline
|
|
547
|
-
if (metrics.hookExecutionRate !== undefined) {
|
|
548
|
-
const rate = metrics.hookExecutionRate;
|
|
549
|
-
// Assume 10 ops/min is baseline
|
|
550
|
-
const throughputScore = Math.min(100, (rate / 10) * 100);
|
|
551
|
-
this.updateSignal('perf-throughput', throughputScore, {
|
|
552
|
-
message: `${rate} ops/min`,
|
|
553
|
-
metadata: { rate },
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Memory score: based on heap usage
|
|
558
|
-
if (metrics.memoryUsage) {
|
|
559
|
-
const heapUsed = metrics.memoryUsage.heapUsed || 0;
|
|
560
|
-
const heapTotal = metrics.memoryUsage.heapTotal || 1;
|
|
561
|
-
const memPct = (heapUsed / heapTotal) * 100;
|
|
562
|
-
const memScore = Math.max(0, 100 - memPct);
|
|
563
|
-
this.updateSignal('perf-memory', memScore, {
|
|
564
|
-
message: `Heap: ${(heapUsed / 1024 / 1024).toFixed(1)}MB / ${(heapTotal / 1024 / 1024).toFixed(1)}MB`,
|
|
565
|
-
metadata: metrics.memoryUsage,
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
* Check if deployment is ready
|
|
572
|
-
* @returns {Object} Deployment readiness result
|
|
573
|
-
*/
|
|
574
|
-
isDeploymentReady() {
|
|
575
|
-
const allSignals = this.getAllSignals();
|
|
576
|
-
|
|
577
|
-
if (allSignals.length === 0) {
|
|
578
|
-
return {
|
|
579
|
-
ready: false,
|
|
580
|
-
reason: 'No signals registered',
|
|
581
|
-
signals: [],
|
|
582
|
-
summary: { green: 0, yellow: 0, red: 0 },
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
const summary = { green: 0, yellow: 0, red: 0 };
|
|
587
|
-
const failures = [];
|
|
588
|
-
const warnings = [];
|
|
589
|
-
|
|
590
|
-
for (const signal of allSignals) {
|
|
591
|
-
summary[signal.state]++;
|
|
592
|
-
|
|
593
|
-
if (signal.state === AndonState.RED) {
|
|
594
|
-
failures.push(signal);
|
|
595
|
-
} else if (signal.state === AndonState.YELLOW) {
|
|
596
|
-
warnings.push(signal);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// Check required signals
|
|
601
|
-
const missingRequired = this.config.requiredSignals.filter(name => !this.signals.has(name));
|
|
602
|
-
|
|
603
|
-
if (missingRequired.length > 0) {
|
|
604
|
-
return {
|
|
605
|
-
ready: false,
|
|
606
|
-
reason: `Missing required signals: ${missingRequired.join(', ')}`,
|
|
607
|
-
signals: allSignals,
|
|
608
|
-
summary,
|
|
609
|
-
missingRequired,
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Strict mode: all must be GREEN
|
|
614
|
-
if (this.config.strictDeployment) {
|
|
615
|
-
const ready = failures.length === 0 && warnings.length === 0;
|
|
616
|
-
return {
|
|
617
|
-
ready,
|
|
618
|
-
reason: ready
|
|
619
|
-
? 'All signals GREEN'
|
|
620
|
-
: failures.length > 0
|
|
621
|
-
? `${failures.length} RED signal(s): ${failures.map(s => s.name).join(', ')}`
|
|
622
|
-
: `${warnings.length} YELLOW signal(s): ${warnings.map(s => s.name).join(', ')}`,
|
|
623
|
-
signals: allSignals,
|
|
624
|
-
summary,
|
|
625
|
-
failures,
|
|
626
|
-
warnings,
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// Lenient mode: no RED signals
|
|
631
|
-
const ready = failures.length === 0;
|
|
632
|
-
return {
|
|
633
|
-
ready,
|
|
634
|
-
reason: ready
|
|
635
|
-
? warnings.length > 0
|
|
636
|
-
? `${warnings.length} warning(s), but deployable`
|
|
637
|
-
: 'All signals healthy'
|
|
638
|
-
: `${failures.length} RED signal(s): ${failures.map(s => s.name).join(', ')}`,
|
|
639
|
-
signals: allSignals,
|
|
640
|
-
summary,
|
|
641
|
-
failures,
|
|
642
|
-
warnings,
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
/**
|
|
647
|
-
* Get all registered signals with their current states
|
|
648
|
-
* @returns {Array<Object>} All signal states
|
|
649
|
-
*/
|
|
650
|
-
getAllSignals() {
|
|
651
|
-
return Array.from(this.signals.values());
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
* Get signals by category
|
|
656
|
-
* @param {SignalCategory} category - Signal category
|
|
657
|
-
* @returns {Array<Object>} Signals in category
|
|
658
|
-
*/
|
|
659
|
-
getSignalsByCategory(category) {
|
|
660
|
-
return this.getAllSignals().filter(s => s.category === category);
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* Get the overall system state (worst signal)
|
|
665
|
-
* @returns {AndonState} Overall system state
|
|
666
|
-
*/
|
|
667
|
-
getOverallState() {
|
|
668
|
-
const signals = this.getAllSignals();
|
|
669
|
-
if (signals.length === 0) {
|
|
670
|
-
return AndonState.GREEN;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
return signals.reduce((worst, signal) => getWorstState(worst, signal.state), AndonState.GREEN);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
/**
|
|
677
|
-
* Get weighted overall score
|
|
678
|
-
* @returns {number} Weighted score (0-100)
|
|
679
|
-
*/
|
|
680
|
-
getWeightedScore() {
|
|
681
|
-
let totalWeight = 0;
|
|
682
|
-
let weightedSum = 0;
|
|
683
|
-
|
|
684
|
-
for (const [name, signal] of this.signals) {
|
|
685
|
-
const config = this.signalConfigs.get(name);
|
|
686
|
-
const weight = config?.weight || 1;
|
|
687
|
-
totalWeight += weight;
|
|
688
|
-
weightedSum += signal.score * weight;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
return totalWeight > 0 ? Math.round(weightedSum / totalWeight) : 0;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
/**
|
|
695
|
-
* Subscribe to signal changes
|
|
696
|
-
* @param {Function} callback - Callback function
|
|
697
|
-
* @returns {Function} Unsubscribe function
|
|
698
|
-
*/
|
|
699
|
-
onSignalChange(callback) {
|
|
700
|
-
if (typeof callback !== 'function') {
|
|
701
|
-
throw new TypeError('Callback must be a function');
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
this.listeners.push(callback);
|
|
705
|
-
|
|
706
|
-
return () => {
|
|
707
|
-
const index = this.listeners.indexOf(callback);
|
|
708
|
-
if (index > -1) {
|
|
709
|
-
this.listeners.splice(index, 1);
|
|
710
|
-
}
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
/**
|
|
715
|
-
* Get signal history
|
|
716
|
-
* @param {string} name - Signal name
|
|
717
|
-
* @param {number} [limit=10] - Maximum entries to return
|
|
718
|
-
* @returns {Array<Object>} Signal history
|
|
719
|
-
*/
|
|
720
|
-
getSignalHistory(name, limit = 10) {
|
|
721
|
-
const history = this.history.get(name) || [];
|
|
722
|
-
return history.slice(-limit);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* Clear all signals
|
|
727
|
-
*/
|
|
728
|
-
clear() {
|
|
729
|
-
this.signals.clear();
|
|
730
|
-
this.signalConfigs.clear();
|
|
731
|
-
this.history.clear();
|
|
732
|
-
this.listeners = [];
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
/**
|
|
736
|
-
* Generate a dashboard-friendly summary
|
|
737
|
-
* @returns {Object} Dashboard summary
|
|
738
|
-
*/
|
|
739
|
-
getDashboardSummary() {
|
|
740
|
-
const _signals = this.getAllSignals();
|
|
741
|
-
const deployment = this.isDeploymentReady();
|
|
742
|
-
|
|
743
|
-
return {
|
|
744
|
-
overallState: this.getOverallState(),
|
|
745
|
-
overallScore: this.getWeightedScore(),
|
|
746
|
-
deployment: {
|
|
747
|
-
ready: deployment.ready,
|
|
748
|
-
reason: deployment.reason,
|
|
749
|
-
},
|
|
750
|
-
summary: deployment.summary,
|
|
751
|
-
byCategory: {
|
|
752
|
-
validation: this.getSignalsByCategory(SignalCategory.VALIDATION),
|
|
753
|
-
cicd: this.getSignalsByCategory(SignalCategory.CI_CD),
|
|
754
|
-
performance: this.getSignalsByCategory(SignalCategory.PERFORMANCE),
|
|
755
|
-
security: this.getSignalsByCategory(SignalCategory.SECURITY),
|
|
756
|
-
health: this.getSignalsByCategory(SignalCategory.HEALTH),
|
|
757
|
-
},
|
|
758
|
-
timestamp: Date.now(),
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
/**
|
|
764
|
-
* Create an Andon Signal Manager instance
|
|
765
|
-
* @param {Object} [config] - Configuration
|
|
766
|
-
* @returns {AndonSignalManager} Manager instance
|
|
767
|
-
*/
|
|
768
|
-
export function createAndonSignalManager(config = {}) {
|
|
769
|
-
return new AndonSignalManager(config);
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
/**
|
|
773
|
-
* Default Andon Signal Manager instance
|
|
774
|
-
*/
|
|
775
|
-
export const defaultAndonSignalManager = createAndonSignalManager();
|