@unrdf/knowledge-engine 5.0.1 → 26.4.2

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 (71) hide show
  1. package/package.json +23 -17
  2. package/src/ai-enhanced-search.mjs +371 -0
  3. package/src/anomaly-detector.mjs +226 -0
  4. package/src/artifact-generator.mjs +252 -0
  5. package/src/browser.mjs +1 -1
  6. package/src/chatman/disruption-arithmetic.mjs +140 -0
  7. package/src/chatman/market-dynamics.mjs +140 -0
  8. package/src/chatman/organizational-dynamics.mjs +140 -0
  9. package/src/chatman/strategic-dynamics.mjs +140 -0
  10. package/src/chatman-config-loader.mjs +282 -0
  11. package/src/chatman-engine.mjs +435 -0
  12. package/src/chatman-operator.mjs +343 -0
  13. package/src/dark-field-detector.mjs +332 -0
  14. package/src/formation-theorems.mjs +345 -0
  15. package/src/index.mjs +20 -2
  16. package/src/knowledge-hook-manager.mjs +1 -1
  17. package/src/lockchain-writer-browser.mjs +2 -2
  18. package/src/observability.mjs +40 -4
  19. package/src/query-optimizer.mjs +1 -1
  20. package/src/resolution-layer.mjs +1 -1
  21. package/src/transaction.mjs +11 -9
  22. package/README.md +0 -84
  23. package/src/browser-shims.mjs +0 -343
  24. package/src/canonicalize.mjs +0 -414
  25. package/src/condition-cache.mjs +0 -109
  26. package/src/condition-evaluator.mjs +0 -722
  27. package/src/dark-matter-core.mjs +0 -742
  28. package/src/define-hook.mjs +0 -213
  29. package/src/effect-sandbox-browser.mjs +0 -283
  30. package/src/effect-sandbox-worker.mjs +0 -170
  31. package/src/effect-sandbox.mjs +0 -517
  32. package/src/engines/index.mjs +0 -11
  33. package/src/engines/rdf-engine.mjs +0 -299
  34. package/src/file-resolver.mjs +0 -387
  35. package/src/hook-executor-batching.mjs +0 -277
  36. package/src/hook-executor.mjs +0 -870
  37. package/src/hook-management.mjs +0 -150
  38. package/src/ken-parliment.mjs +0 -119
  39. package/src/ken.mjs +0 -149
  40. package/src/knowledge-engine/builtin-rules.mjs +0 -190
  41. package/src/knowledge-engine/inference-engine.mjs +0 -418
  42. package/src/knowledge-engine/knowledge-engine.mjs +0 -317
  43. package/src/knowledge-engine/pattern-dsl.mjs +0 -142
  44. package/src/knowledge-engine/pattern-matcher.mjs +0 -215
  45. package/src/knowledge-engine/rules.mjs +0 -184
  46. package/src/knowledge-engine.mjs +0 -319
  47. package/src/knowledge-hook-engine.mjs +0 -360
  48. package/src/knowledge-substrate-core.mjs +0 -927
  49. package/src/lite.mjs +0 -222
  50. package/src/lockchain-writer.mjs +0 -602
  51. package/src/monitoring/andon-signals.mjs +0 -775
  52. package/src/parse.mjs +0 -290
  53. package/src/performance-optimizer.mjs +0 -678
  54. package/src/policy-pack.mjs +0 -572
  55. package/src/query-cache.mjs +0 -116
  56. package/src/query.mjs +0 -306
  57. package/src/reason.mjs +0 -350
  58. package/src/schemas.mjs +0 -1063
  59. package/src/security/error-sanitizer.mjs +0 -257
  60. package/src/security/path-validator.mjs +0 -194
  61. package/src/security/sandbox-restrictions.mjs +0 -331
  62. package/src/security-validator.mjs +0 -389
  63. package/src/store-cache.mjs +0 -137
  64. package/src/telemetry.mjs +0 -167
  65. package/src/utils/adaptive-monitor.mjs +0 -746
  66. package/src/utils/circuit-breaker.mjs +0 -513
  67. package/src/utils/edge-case-handler.mjs +0 -503
  68. package/src/utils/memory-manager.mjs +0 -498
  69. package/src/utils/ring-buffer.mjs +0 -282
  70. package/src/validate.mjs +0 -319
  71. 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();