@lssm/lib.observability 0.0.0-canary-20251120192244 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.turbo/turbo-build.log +92 -29
  2. package/.turbo/turbo-lint.log +20 -0
  3. package/CHANGELOG.md +12 -1
  4. package/README.md +6 -0
  5. package/dist/anomaly/alert-manager.d.mts +21 -0
  6. package/dist/anomaly/alert-manager.d.mts.map +1 -0
  7. package/dist/anomaly/alert-manager.mjs +2 -0
  8. package/dist/anomaly/alert-manager.mjs.map +1 -0
  9. package/dist/anomaly/anomaly-detector.d.mts +26 -0
  10. package/dist/anomaly/anomaly-detector.d.mts.map +1 -0
  11. package/dist/anomaly/anomaly-detector.mjs +2 -0
  12. package/dist/anomaly/anomaly-detector.mjs.map +1 -0
  13. package/dist/anomaly/baseline-calculator.d.mts +26 -0
  14. package/dist/anomaly/baseline-calculator.d.mts.map +1 -0
  15. package/dist/anomaly/baseline-calculator.mjs +2 -0
  16. package/dist/anomaly/baseline-calculator.mjs.map +1 -0
  17. package/dist/anomaly/root-cause-analyzer.d.mts +23 -0
  18. package/dist/anomaly/root-cause-analyzer.d.mts.map +1 -0
  19. package/dist/anomaly/root-cause-analyzer.mjs +2 -0
  20. package/dist/anomaly/root-cause-analyzer.mjs.map +1 -0
  21. package/dist/index.d.mts +10 -2
  22. package/dist/index.mjs +1 -1
  23. package/dist/intent/aggregator.d.mts +60 -0
  24. package/dist/intent/aggregator.d.mts.map +1 -0
  25. package/dist/intent/aggregator.mjs +2 -0
  26. package/dist/intent/aggregator.mjs.map +1 -0
  27. package/dist/intent/detector.d.mts +32 -0
  28. package/dist/intent/detector.d.mts.map +1 -0
  29. package/dist/intent/detector.mjs +2 -0
  30. package/dist/intent/detector.mjs.map +1 -0
  31. package/dist/lifecycle/dist/index.mjs +1 -0
  32. package/dist/lifecycle/dist/types/axes.mjs +2 -0
  33. package/dist/lifecycle/dist/types/axes.mjs.map +1 -0
  34. package/dist/lifecycle/dist/types/milestones.mjs +1 -0
  35. package/dist/lifecycle/dist/types/signals.mjs +1 -0
  36. package/dist/lifecycle/dist/types/stages.mjs +2 -0
  37. package/dist/lifecycle/dist/types/stages.mjs.map +1 -0
  38. package/dist/lifecycle/dist/utils/formatters.mjs +2 -0
  39. package/dist/lifecycle/dist/utils/formatters.mjs.map +1 -0
  40. package/dist/logging/index.mjs.map +1 -1
  41. package/dist/metrics/index.mjs.map +1 -1
  42. package/dist/pipeline/evolution-pipeline.d.mts +40 -0
  43. package/dist/pipeline/evolution-pipeline.d.mts.map +1 -0
  44. package/dist/pipeline/evolution-pipeline.mjs +2 -0
  45. package/dist/pipeline/evolution-pipeline.mjs.map +1 -0
  46. package/dist/pipeline/lifecycle-pipeline.d.mts +44 -0
  47. package/dist/pipeline/lifecycle-pipeline.d.mts.map +1 -0
  48. package/dist/pipeline/lifecycle-pipeline.mjs +2 -0
  49. package/dist/pipeline/lifecycle-pipeline.mjs.map +1 -0
  50. package/dist/tracing/index.mjs.map +1 -1
  51. package/dist/tracing/middleware.d.mts +16 -2
  52. package/dist/tracing/middleware.d.mts.map +1 -1
  53. package/dist/tracing/middleware.mjs +1 -1
  54. package/dist/tracing/middleware.mjs.map +1 -1
  55. package/package.json +12 -1
  56. package/src/anomaly/alert-manager.ts +31 -0
  57. package/src/anomaly/anomaly-detector.ts +94 -0
  58. package/src/anomaly/baseline-calculator.ts +54 -0
  59. package/src/anomaly/root-cause-analyzer.ts +55 -0
  60. package/src/index.ts +35 -1
  61. package/src/intent/aggregator.ts +161 -0
  62. package/src/intent/detector.ts +187 -0
  63. package/src/logging/index.ts +0 -1
  64. package/src/metrics/index.ts +0 -1
  65. package/src/pipeline/evolution-pipeline.ts +90 -0
  66. package/src/pipeline/lifecycle-pipeline.ts +105 -0
  67. package/src/tracing/index.ts +0 -1
  68. package/src/tracing/middleware.ts +76 -1
  69. package/tsconfig.json +6 -0
  70. package/tsconfig.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["serviceName: string","entry: LogEntry"],"sources":["../../src/logging/index.ts"],"sourcesContent":["import { trace, context } from '@opentelemetry/api';\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nexport interface LogEntry {\n level: LogLevel;\n message: string;\n [key: string]: unknown;\n}\n\nexport class Logger {\n constructor(private readonly serviceName: string) {}\n\n private log(\n level: LogLevel,\n message: string,\n meta: Record<string, unknown> = {}\n ) {\n const span = trace.getSpan(context.active());\n const traceId = span?.spanContext().traceId;\n const spanId = span?.spanContext().spanId;\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n service: this.serviceName,\n level,\n message,\n traceId,\n spanId,\n ...meta,\n };\n\n // structured logging to stdout\n console.log(JSON.stringify(entry));\n }\n\n debug(message: string, meta?: Record<string, unknown>) {\n this.log('debug', message, meta);\n }\n\n info(message: string, meta?: Record<string, unknown>) {\n this.log('info', message, meta);\n }\n\n warn(message: string, meta?: Record<string, unknown>) {\n this.log('warn', message, meta);\n }\n\n error(message: string, meta?: Record<string, unknown>) {\n this.log('error', message, meta);\n }\n}\n\nexport const logger = new Logger(\n process.env.OTEL_SERVICE_NAME || 'unknown-service'\n);\n\n"],"mappings":"wDAUA,IAAa,EAAb,KAAoB,CAClB,YAAY,EAAsC,CAArB,KAAA,YAAA,EAE7B,IACE,EACA,EACA,EAAgC,EAAE,CAClC,CACA,IAAM,EAAO,EAAM,QAAQ,EAAQ,QAAQ,CAAC,CACtC,EAAU,GAAM,aAAa,CAAC,QAC9B,EAAS,GAAM,aAAa,CAAC,OAE7BC,EAAkB,CACtB,UAAW,IAAI,MAAM,CAAC,aAAa,CACnC,QAAS,KAAK,YACd,QACA,UACA,UACA,SACA,GAAG,EACJ,CAGD,QAAQ,IAAI,KAAK,UAAU,EAAM,CAAC,CAGpC,MAAM,EAAiB,EAAgC,CACrD,KAAK,IAAI,QAAS,EAAS,EAAK,CAGlC,KAAK,EAAiB,EAAgC,CACpD,KAAK,IAAI,OAAQ,EAAS,EAAK,CAGjC,KAAK,EAAiB,EAAgC,CACpD,KAAK,IAAI,OAAQ,EAAS,EAAK,CAGjC,MAAM,EAAiB,EAAgC,CACrD,KAAK,IAAI,QAAS,EAAS,EAAK,GAIpC,MAAa,EAAS,IAAI,EACxB,QAAQ,IAAI,mBAAqB,kBAClC"}
1
+ {"version":3,"file":"index.mjs","names":["serviceName: string","entry: LogEntry"],"sources":["../../src/logging/index.ts"],"sourcesContent":["import { trace, context } from '@opentelemetry/api';\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nexport interface LogEntry {\n level: LogLevel;\n message: string;\n [key: string]: unknown;\n}\n\nexport class Logger {\n constructor(private readonly serviceName: string) {}\n\n private log(\n level: LogLevel,\n message: string,\n meta: Record<string, unknown> = {}\n ) {\n const span = trace.getSpan(context.active());\n const traceId = span?.spanContext().traceId;\n const spanId = span?.spanContext().spanId;\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n service: this.serviceName,\n level,\n message,\n traceId,\n spanId,\n ...meta,\n };\n\n // structured logging to stdout\n console.log(JSON.stringify(entry));\n }\n\n debug(message: string, meta?: Record<string, unknown>) {\n this.log('debug', message, meta);\n }\n\n info(message: string, meta?: Record<string, unknown>) {\n this.log('info', message, meta);\n }\n\n warn(message: string, meta?: Record<string, unknown>) {\n this.log('warn', message, meta);\n }\n\n error(message: string, meta?: Record<string, unknown>) {\n this.log('error', message, meta);\n }\n}\n\nexport const logger = new Logger(\n process.env.OTEL_SERVICE_NAME || 'unknown-service'\n);\n"],"mappings":"wDAUA,IAAa,EAAb,KAAoB,CAClB,YAAY,EAAsC,CAArB,KAAA,YAAA,EAE7B,IACE,EACA,EACA,EAAgC,EAAE,CAClC,CACA,IAAM,EAAO,EAAM,QAAQ,EAAQ,QAAQ,CAAC,CACtC,EAAU,GAAM,aAAa,CAAC,QAC9B,EAAS,GAAM,aAAa,CAAC,OAE7BC,EAAkB,CACtB,UAAW,IAAI,MAAM,CAAC,aAAa,CACnC,QAAS,KAAK,YACd,QACA,UACA,UACA,SACA,GAAG,EACJ,CAGD,QAAQ,IAAI,KAAK,UAAU,EAAM,CAAC,CAGpC,MAAM,EAAiB,EAAgC,CACrD,KAAK,IAAI,QAAS,EAAS,EAAK,CAGlC,KAAK,EAAiB,EAAgC,CACpD,KAAK,IAAI,OAAQ,EAAS,EAAK,CAGjC,KAAK,EAAiB,EAAgC,CACpD,KAAK,IAAI,OAAQ,EAAS,EAAK,CAGjC,MAAM,EAAiB,EAAgC,CACrD,KAAK,IAAI,QAAS,EAAS,EAAK,GAIpC,MAAa,EAAS,IAAI,EACxB,QAAQ,IAAI,mBAAqB,kBAClC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/metrics/index.ts"],"sourcesContent":["import {\n metrics,\n type Meter,\n type Counter,\n type Histogram,\n type UpDownCounter,\n} from '@opentelemetry/api';\n\nconst DEFAULT_METER_NAME = '@lssm/lib.observability';\n\nexport function getMeter(name: string = DEFAULT_METER_NAME): Meter {\n return metrics.getMeter(name);\n}\n\nexport function createCounter(\n name: string,\n description?: string,\n meterName?: string\n): Counter {\n return getMeter(meterName).createCounter(name, { description });\n}\n\nexport function createUpDownCounter(\n name: string,\n description?: string,\n meterName?: string\n): UpDownCounter {\n return getMeter(meterName).createUpDownCounter(name, { description });\n}\n\nexport function createHistogram(\n name: string,\n description?: string,\n meterName?: string\n): Histogram {\n return getMeter(meterName).createHistogram(name, { description });\n}\n\nexport const standardMetrics = {\n httpRequests: createCounter('http_requests_total', 'Total HTTP requests'),\n httpDuration: createHistogram(\n 'http_request_duration_seconds',\n 'HTTP request duration'\n ),\n operationErrors: createCounter(\n 'operation_errors_total',\n 'Total operation errors'\n ),\n workflowDuration: createHistogram(\n 'workflow_duration_seconds',\n 'Workflow execution duration'\n ),\n};\n\n"],"mappings":"6CAUA,SAAgB,EAAS,EAAe,0BAA2B,CACjE,OAAO,EAAQ,SAAS,EAAK,CAG/B,SAAgB,EACd,EACA,EACA,EACS,CACT,OAAO,EAAS,EAAU,CAAC,cAAc,EAAM,CAAE,cAAa,CAAC,CAGjE,SAAgB,EACd,EACA,EACA,EACe,CACf,OAAO,EAAS,EAAU,CAAC,oBAAoB,EAAM,CAAE,cAAa,CAAC,CAGvE,SAAgB,EACd,EACA,EACA,EACW,CACX,OAAO,EAAS,EAAU,CAAC,gBAAgB,EAAM,CAAE,cAAa,CAAC,CAGnE,MAAa,EAAkB,CAC7B,aAAc,EAAc,sBAAuB,sBAAsB,CACzE,aAAc,EACZ,gCACA,wBACD,CACD,gBAAiB,EACf,yBACA,yBACD,CACD,iBAAkB,EAChB,4BACA,8BACD,CACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/metrics/index.ts"],"sourcesContent":["import {\n metrics,\n type Meter,\n type Counter,\n type Histogram,\n type UpDownCounter,\n} from '@opentelemetry/api';\n\nconst DEFAULT_METER_NAME = '@lssm/lib.observability';\n\nexport function getMeter(name: string = DEFAULT_METER_NAME): Meter {\n return metrics.getMeter(name);\n}\n\nexport function createCounter(\n name: string,\n description?: string,\n meterName?: string\n): Counter {\n return getMeter(meterName).createCounter(name, { description });\n}\n\nexport function createUpDownCounter(\n name: string,\n description?: string,\n meterName?: string\n): UpDownCounter {\n return getMeter(meterName).createUpDownCounter(name, { description });\n}\n\nexport function createHistogram(\n name: string,\n description?: string,\n meterName?: string\n): Histogram {\n return getMeter(meterName).createHistogram(name, { description });\n}\n\nexport const standardMetrics = {\n httpRequests: createCounter('http_requests_total', 'Total HTTP requests'),\n httpDuration: createHistogram(\n 'http_request_duration_seconds',\n 'HTTP request duration'\n ),\n operationErrors: createCounter(\n 'operation_errors_total',\n 'Total operation errors'\n ),\n workflowDuration: createHistogram(\n 'workflow_duration_seconds',\n 'Workflow execution duration'\n ),\n};\n"],"mappings":"6CAUA,SAAgB,EAAS,EAAe,0BAA2B,CACjE,OAAO,EAAQ,SAAS,EAAK,CAG/B,SAAgB,EACd,EACA,EACA,EACS,CACT,OAAO,EAAS,EAAU,CAAC,cAAc,EAAM,CAAE,cAAa,CAAC,CAGjE,SAAgB,EACd,EACA,EACA,EACe,CACf,OAAO,EAAS,EAAU,CAAC,oBAAoB,EAAM,CAAE,cAAa,CAAC,CAGvE,SAAgB,EACd,EACA,EACA,EACW,CACX,OAAO,EAAS,EAAU,CAAC,gBAAgB,EAAM,CAAE,cAAa,CAAC,CAGnE,MAAa,EAAkB,CAC7B,aAAc,EAAc,sBAAuB,sBAAsB,CACzE,aAAc,EACZ,gCACA,wBACD,CACD,gBAAiB,EACf,yBACA,yBACD,CACD,iBAAkB,EAChB,4BACA,8BACD,CACF"}
@@ -0,0 +1,40 @@
1
+ import { IntentAggregator, IntentAggregatorSnapshot, TelemetrySample } from "../intent/aggregator.mjs";
2
+ import { IntentDetector, IntentSignal } from "../intent/detector.mjs";
3
+ import { EventEmitter } from "node:events";
4
+
5
+ //#region src/pipeline/evolution-pipeline.d.ts
6
+ type EvolutionPipelineEvent = {
7
+ type: 'intent.detected';
8
+ payload: IntentSignal;
9
+ } | {
10
+ type: 'telemetry.window';
11
+ payload: {
12
+ sampleCount: number;
13
+ };
14
+ };
15
+ interface EvolutionPipelineOptions {
16
+ detector?: IntentDetector;
17
+ aggregator?: IntentAggregator;
18
+ emitter?: EventEmitter;
19
+ onIntent?: (intent: IntentSignal) => Promise<void> | void;
20
+ onSnapshot?: (snapshot: IntentAggregatorSnapshot) => Promise<void> | void;
21
+ }
22
+ declare class EvolutionPipeline {
23
+ private readonly detector;
24
+ private readonly aggregator;
25
+ private readonly emitter;
26
+ private readonly onIntent?;
27
+ private readonly onSnapshot?;
28
+ private timer?;
29
+ private previousMetrics?;
30
+ constructor(options?: EvolutionPipelineOptions);
31
+ ingest(sample: TelemetrySample): void;
32
+ on(listener: (event: EvolutionPipelineEvent) => void): void;
33
+ start(intervalMs?: number): void;
34
+ stop(): void;
35
+ run(): Promise<void>;
36
+ private emit;
37
+ }
38
+ //#endregion
39
+ export { EvolutionPipeline, EvolutionPipelineEvent, EvolutionPipelineOptions };
40
+ //# sourceMappingURL=evolution-pipeline.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evolution-pipeline.d.mts","names":[],"sources":["../../src/pipeline/evolution-pipeline.ts"],"sourcesContent":[],"mappings":";;;;;KAQY,sBAAA;;EAAA,OAAA,EAC4B,YAD5B;AAIZ,CAAA,GAAiB;EACJ,IAAA,EAAA,kBAAA;EACE,OAAA,EAAA;IACH,WAAA,EAAA,MAAA;EACU,CAAA;CAAiB;AACb,UALT,wBAAA,CAKS;EAA6B,QAAA,CAAA,EAJ1C,cAI0C;EAAO,UAAA,CAAA,EAH/C,gBAG+C;EAGjD,OAAA,CAAA,EALD,YAKkB;EAWP,QAAA,CAAA,EAAA,CAAA,MAAA,EAfD,YAeC,EAAA,GAfgB,OAehB,CAAA,IAAA,CAAA,GAAA,IAAA;EAQN,UAAA,CAAA,EAAA,CAAA,QAAA,EAtBS,wBAsBT,EAAA,GAtBsC,OAsBtC,CAAA,IAAA,CAAA,GAAA,IAAA;;AAsBN,cAzCE,iBAAA,CAyCF;EAAA,iBAAA,QAAA;;;;;;;wBA9BY;iBAQN;uBAIM;;;SAkBZ"}
@@ -0,0 +1,2 @@
1
+ import{IntentAggregator as e}from"../intent/aggregator.mjs";import{IntentDetector as t}from"../intent/detector.mjs";import{EventEmitter as n}from"node:events";var r=class{detector;aggregator;emitter;onIntent;onSnapshot;timer;previousMetrics;constructor(r={}){this.detector=r.detector??new t,this.aggregator=r.aggregator??new e,this.emitter=r.emitter??new n,this.onIntent=r.onIntent,this.onSnapshot=r.onSnapshot}ingest(e){this.aggregator.add(e)}on(e){this.emitter.on(`event`,e)}start(e=300*1e3){this.stop(),this.timer=setInterval(()=>{this.run()},e)}stop(){this.timer&&=(clearInterval(this.timer),void 0)}async run(){let e=this.aggregator.flush();if(this.emit({type:`telemetry.window`,payload:{sampleCount:e.sampleCount}}),this.onSnapshot&&await this.onSnapshot(e),!e.sampleCount)return;let t=this.detector.detectFromMetrics(e.metrics,this.previousMetrics),n=this.detector.detectSequentialIntents(e.sequences);this.previousMetrics=e.metrics;let r=[...t,...n];for(let e of r)this.onIntent&&await this.onIntent(e),this.emit({type:`intent.detected`,payload:e})}emit(e){this.emitter.emit(`event`,e)}};export{r as EvolutionPipeline};
2
+ //# sourceMappingURL=evolution-pipeline.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evolution-pipeline.mjs","names":[],"sources":["../../src/pipeline/evolution-pipeline.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport {\n IntentAggregator,\n type IntentAggregatorSnapshot,\n type TelemetrySample,\n} from '../intent/aggregator';\nimport { IntentDetector, type IntentSignal } from '../intent/detector';\n\nexport type EvolutionPipelineEvent =\n | { type: 'intent.detected'; payload: IntentSignal }\n | { type: 'telemetry.window'; payload: { sampleCount: number } };\n\nexport interface EvolutionPipelineOptions {\n detector?: IntentDetector;\n aggregator?: IntentAggregator;\n emitter?: EventEmitter;\n onIntent?: (intent: IntentSignal) => Promise<void> | void;\n onSnapshot?: (snapshot: IntentAggregatorSnapshot) => Promise<void> | void;\n}\n\nexport class EvolutionPipeline {\n private readonly detector: IntentDetector;\n private readonly aggregator: IntentAggregator;\n private readonly emitter: EventEmitter;\n private readonly onIntent?: (intent: IntentSignal) => Promise<void> | void;\n private readonly onSnapshot?: (\n snapshot: IntentAggregatorSnapshot\n ) => Promise<void> | void;\n private timer?: NodeJS.Timeout;\n private previousMetrics?: ReturnType<IntentAggregator['flush']>['metrics'];\n\n constructor(options: EvolutionPipelineOptions = {}) {\n this.detector = options.detector ?? new IntentDetector();\n this.aggregator = options.aggregator ?? new IntentAggregator();\n this.emitter = options.emitter ?? new EventEmitter();\n this.onIntent = options.onIntent;\n this.onSnapshot = options.onSnapshot;\n }\n\n ingest(sample: TelemetrySample) {\n this.aggregator.add(sample);\n }\n\n on(listener: (event: EvolutionPipelineEvent) => void) {\n this.emitter.on('event', listener);\n }\n\n start(intervalMs = 5 * 60 * 1000) {\n this.stop();\n this.timer = setInterval(() => {\n void this.run();\n }, intervalMs);\n }\n\n stop() {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n }\n\n async run() {\n const snapshot = this.aggregator.flush();\n this.emit({\n type: 'telemetry.window',\n payload: { sampleCount: snapshot.sampleCount },\n });\n if (this.onSnapshot) await this.onSnapshot(snapshot);\n if (!snapshot.sampleCount) return;\n\n const metricSignals = this.detector.detectFromMetrics(\n snapshot.metrics,\n this.previousMetrics\n );\n const sequenceSignals = this.detector.detectSequentialIntents(\n snapshot.sequences\n );\n this.previousMetrics = snapshot.metrics;\n\n const signals = [...metricSignals, ...sequenceSignals];\n for (const signal of signals) {\n if (this.onIntent) await this.onIntent(signal);\n this.emit({ type: 'intent.detected', payload: signal });\n }\n }\n\n private emit(event: EvolutionPipelineEvent) {\n this.emitter.emit('event', event);\n }\n}\n"],"mappings":"+JAoBA,IAAa,EAAb,KAA+B,CAC7B,SACA,WACA,QACA,SACA,WAGA,MACA,gBAEA,YAAY,EAAoC,EAAE,CAAE,CAClD,KAAK,SAAW,EAAQ,UAAY,IAAI,EACxC,KAAK,WAAa,EAAQ,YAAc,IAAI,EAC5C,KAAK,QAAU,EAAQ,SAAW,IAAI,EACtC,KAAK,SAAW,EAAQ,SACxB,KAAK,WAAa,EAAQ,WAG5B,OAAO,EAAyB,CAC9B,KAAK,WAAW,IAAI,EAAO,CAG7B,GAAG,EAAmD,CACpD,KAAK,QAAQ,GAAG,QAAS,EAAS,CAGpC,MAAM,EAAa,IAAS,IAAM,CAChC,KAAK,MAAM,CACX,KAAK,MAAQ,gBAAkB,CACxB,KAAK,KAAK,EACd,EAAW,CAGhB,MAAO,CACL,AAEE,KAAK,SADL,cAAc,KAAK,MAAM,CACZ,IAAA,IAIjB,MAAM,KAAM,CACV,IAAM,EAAW,KAAK,WAAW,OAAO,CAMxC,GALA,KAAK,KAAK,CACR,KAAM,mBACN,QAAS,CAAE,YAAa,EAAS,YAAa,CAC/C,CAAC,CACE,KAAK,YAAY,MAAM,KAAK,WAAW,EAAS,CAChD,CAAC,EAAS,YAAa,OAE3B,IAAM,EAAgB,KAAK,SAAS,kBAClC,EAAS,QACT,KAAK,gBACN,CACK,EAAkB,KAAK,SAAS,wBACpC,EAAS,UACV,CACD,KAAK,gBAAkB,EAAS,QAEhC,IAAM,EAAU,CAAC,GAAG,EAAe,GAAG,EAAgB,CACtD,IAAK,IAAM,KAAU,EACf,KAAK,UAAU,MAAM,KAAK,SAAS,EAAO,CAC9C,KAAK,KAAK,CAAE,KAAM,kBAAmB,QAAS,EAAQ,CAAC,CAI3D,KAAa,EAA+B,CAC1C,KAAK,QAAQ,KAAK,QAAS,EAAM"}
@@ -0,0 +1,44 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { LifecycleAssessment, LifecycleStage } from "@lssm/lib.lifecycle";
3
+
4
+ //#region src/pipeline/lifecycle-pipeline.d.ts
5
+ type LifecyclePipelineEvent = {
6
+ type: 'assessment.recorded';
7
+ payload: {
8
+ tenantId?: string;
9
+ stage: LifecycleStage;
10
+ };
11
+ } | {
12
+ type: 'stage.changed';
13
+ payload: {
14
+ tenantId?: string;
15
+ previousStage?: LifecycleStage;
16
+ nextStage: LifecycleStage;
17
+ };
18
+ } | {
19
+ type: 'confidence.low';
20
+ payload: {
21
+ tenantId?: string;
22
+ confidence: number;
23
+ };
24
+ };
25
+ interface LifecycleKpiPipelineOptions {
26
+ meterName?: string;
27
+ emitter?: EventEmitter;
28
+ lowConfidenceThreshold?: number;
29
+ }
30
+ declare class LifecycleKpiPipeline {
31
+ private readonly assessmentCounter;
32
+ private readonly confidenceHistogram;
33
+ private readonly stageUpDownCounter;
34
+ private readonly emitter;
35
+ private readonly lowConfidenceThreshold;
36
+ private readonly currentStageByTenant;
37
+ constructor(options?: LifecycleKpiPipelineOptions);
38
+ recordAssessment(assessment: LifecycleAssessment, tenantId?: string): void;
39
+ on(listener: (event: LifecyclePipelineEvent) => void): void;
40
+ private ensureStageCounters;
41
+ }
42
+ //#endregion
43
+ export { LifecycleKpiPipeline, LifecycleKpiPipelineOptions, LifecyclePipelineEvent };
44
+ //# sourceMappingURL=lifecycle-pipeline.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle-pipeline.d.mts","names":[],"sources":["../../src/pipeline/lifecycle-pipeline.ts"],"sourcesContent":[],"mappings":";;;;KASY,sBAAA;;EAAA,OAAA,EAAA;IAG+B,QAAA,CAAA,EAAA,MAAA;IAMnB,KAAA,EANmB,cAMnB;EACL,CAAA;CAAc,GAAA;EAQhB,IAAA,EAAA,eAAA;EAMJ,OAAA,EAAA;IAQU,QAAA,CAAA,EAAA,MAAA;IAqBQ,aAAA,CAAA,EA5CP,cA4CO;IAoBR,SAAA,EA/DJ,cA+DI;EAAsB,CAAA;;;;;;;;UAvD5B,2BAAA;;YAEL;;;cAIC,oBAAA;;;;;;;wBAQU;+BAqBQ;uBAoBR"}
@@ -0,0 +1,2 @@
1
+ import{createCounter as e,createHistogram as t,createUpDownCounter as n}from"../metrics/index.mjs";import{o as r}from"../lifecycle/dist/utils/formatters.mjs";import"../lifecycle/dist/index.mjs";import{EventEmitter as i}from"node:events";var a=class{assessmentCounter;confidenceHistogram;stageUpDownCounter;emitter;lowConfidenceThreshold;currentStageByTenant=new Map;constructor(r={}){let a=r.meterName??`@lssm/lib.lifecycle-kpi`;this.assessmentCounter=e(`lifecycle_assessments_total`,`Total lifecycle assessments`,a),this.confidenceHistogram=t(`lifecycle_assessment_confidence`,`Lifecycle assessment confidence distribution`,a),this.stageUpDownCounter=n(`lifecycle_stage_tenants`,`Current tenants per lifecycle stage`,a),this.emitter=r.emitter??new i,this.lowConfidenceThreshold=r.lowConfidenceThreshold??.4}recordAssessment(e,t){let n={stage:r(e.stage),tenantId:t};this.assessmentCounter.add(1,n),this.confidenceHistogram.record(e.confidence,n),this.ensureStageCounters(e.stage,t),this.emitter.emit(`event`,{type:`assessment.recorded`,payload:{tenantId:t,stage:e.stage}}),e.confidence<this.lowConfidenceThreshold&&this.emitter.emit(`event`,{type:`confidence.low`,payload:{tenantId:t,confidence:e.confidence}})}on(e){this.emitter.on(`event`,e)}ensureStageCounters(e,t){if(!t)return;let n=this.currentStageByTenant.get(t);n!==e&&(n!==void 0&&this.stageUpDownCounter.add(-1,{stage:r(n),tenantId:t}),this.stageUpDownCounter.add(1,{stage:r(e),tenantId:t}),this.currentStageByTenant.set(t,e),this.emitter.emit(`event`,{type:`stage.changed`,payload:{tenantId:t,previousStage:n,nextStage:e}}))}};export{a as LifecycleKpiPipeline};
2
+ //# sourceMappingURL=lifecycle-pipeline.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle-pipeline.mjs","names":["getStageLabel"],"sources":["../../src/pipeline/lifecycle-pipeline.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport type { LifecycleAssessment, LifecycleStage } from '@lssm/lib.lifecycle';\nimport { getStageLabel } from '@lssm/lib.lifecycle';\nimport {\n createCounter,\n createHistogram,\n createUpDownCounter,\n} from '../metrics';\n\nexport type LifecyclePipelineEvent =\n | {\n type: 'assessment.recorded';\n payload: { tenantId?: string; stage: LifecycleStage };\n }\n | {\n type: 'stage.changed';\n payload: {\n tenantId?: string;\n previousStage?: LifecycleStage;\n nextStage: LifecycleStage;\n };\n }\n | {\n type: 'confidence.low';\n payload: { tenantId?: string; confidence: number };\n };\n\nexport interface LifecycleKpiPipelineOptions {\n meterName?: string;\n emitter?: EventEmitter;\n lowConfidenceThreshold?: number;\n}\n\nexport class LifecycleKpiPipeline {\n private readonly assessmentCounter;\n private readonly confidenceHistogram;\n private readonly stageUpDownCounter;\n private readonly emitter: EventEmitter;\n private readonly lowConfidenceThreshold: number;\n private readonly currentStageByTenant = new Map<string, LifecycleStage>();\n\n constructor(options: LifecycleKpiPipelineOptions = {}) {\n const meterName = options.meterName ?? '@lssm/lib.lifecycle-kpi';\n this.assessmentCounter = createCounter(\n 'lifecycle_assessments_total',\n 'Total lifecycle assessments',\n meterName\n );\n this.confidenceHistogram = createHistogram(\n 'lifecycle_assessment_confidence',\n 'Lifecycle assessment confidence distribution',\n meterName\n );\n this.stageUpDownCounter = createUpDownCounter(\n 'lifecycle_stage_tenants',\n 'Current tenants per lifecycle stage',\n meterName\n );\n this.emitter = options.emitter ?? new EventEmitter();\n this.lowConfidenceThreshold = options.lowConfidenceThreshold ?? 0.4;\n }\n\n recordAssessment(assessment: LifecycleAssessment, tenantId?: string) {\n const stageLabel = getStageLabel(assessment.stage);\n const attributes = { stage: stageLabel, tenantId };\n this.assessmentCounter.add(1, attributes);\n this.confidenceHistogram.record(assessment.confidence, attributes);\n\n this.ensureStageCounters(assessment.stage, tenantId);\n this.emitter.emit('event', {\n type: 'assessment.recorded',\n payload: { tenantId, stage: assessment.stage },\n } satisfies LifecyclePipelineEvent);\n\n if (assessment.confidence < this.lowConfidenceThreshold) {\n this.emitter.emit('event', {\n type: 'confidence.low',\n payload: { tenantId, confidence: assessment.confidence },\n } satisfies LifecyclePipelineEvent);\n }\n }\n\n on(listener: (event: LifecyclePipelineEvent) => void) {\n this.emitter.on('event', listener);\n }\n\n private ensureStageCounters(stage: LifecycleStage, tenantId?: string) {\n if (!tenantId) return;\n const previous = this.currentStageByTenant.get(tenantId);\n if (previous === stage) return;\n\n if (previous !== undefined) {\n this.stageUpDownCounter.add(-1, {\n stage: getStageLabel(previous),\n tenantId,\n });\n }\n this.stageUpDownCounter.add(1, { stage: getStageLabel(stage), tenantId });\n this.currentStageByTenant.set(tenantId, stage);\n this.emitter.emit('event', {\n type: 'stage.changed',\n payload: { tenantId, previousStage: previous, nextStage: stage },\n } satisfies LifecyclePipelineEvent);\n }\n}\n"],"mappings":"6OAiCA,IAAa,EAAb,KAAkC,CAChC,kBACA,oBACA,mBACA,QACA,uBACA,qBAAwC,IAAI,IAE5C,YAAY,EAAuC,EAAE,CAAE,CACrD,IAAM,EAAY,EAAQ,WAAa,0BACvC,KAAK,kBAAoB,EACvB,8BACA,8BACA,EACD,CACD,KAAK,oBAAsB,EACzB,kCACA,+CACA,EACD,CACD,KAAK,mBAAqB,EACxB,0BACA,sCACA,EACD,CACD,KAAK,QAAU,EAAQ,SAAW,IAAI,EACtC,KAAK,uBAAyB,EAAQ,wBAA0B,GAGlE,iBAAiB,EAAiC,EAAmB,CAEnE,IAAM,EAAa,CAAE,MADFA,EAAc,EAAW,MAAM,CACV,WAAU,CAClD,KAAK,kBAAkB,IAAI,EAAG,EAAW,CACzC,KAAK,oBAAoB,OAAO,EAAW,WAAY,EAAW,CAElE,KAAK,oBAAoB,EAAW,MAAO,EAAS,CACpD,KAAK,QAAQ,KAAK,QAAS,CACzB,KAAM,sBACN,QAAS,CAAE,WAAU,MAAO,EAAW,MAAO,CAC/C,CAAkC,CAE/B,EAAW,WAAa,KAAK,wBAC/B,KAAK,QAAQ,KAAK,QAAS,CACzB,KAAM,iBACN,QAAS,CAAE,WAAU,WAAY,EAAW,WAAY,CACzD,CAAkC,CAIvC,GAAG,EAAmD,CACpD,KAAK,QAAQ,GAAG,QAAS,EAAS,CAGpC,oBAA4B,EAAuB,EAAmB,CACpE,GAAI,CAAC,EAAU,OACf,IAAM,EAAW,KAAK,qBAAqB,IAAI,EAAS,CACpD,IAAa,IAEb,IAAa,IAAA,IACf,KAAK,mBAAmB,IAAI,GAAI,CAC9B,MAAOA,EAAc,EAAS,CAC9B,WACD,CAAC,CAEJ,KAAK,mBAAmB,IAAI,EAAG,CAAE,MAAOA,EAAc,EAAM,CAAE,WAAU,CAAC,CACzE,KAAK,qBAAqB,IAAI,EAAU,EAAM,CAC9C,KAAK,QAAQ,KAAK,QAAS,CACzB,KAAM,gBACN,QAAS,CAAE,WAAU,cAAe,EAAU,UAAW,EAAO,CACjE,CAAkC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/tracing/index.ts"],"sourcesContent":["import {\n trace,\n context,\n SpanStatusCode,\n type Span,\n type Tracer,\n} from '@opentelemetry/api';\n\nconst DEFAULT_TRACER_NAME = '@lssm/lib.observability';\n\nexport function getTracer(name: string = DEFAULT_TRACER_NAME): Tracer {\n return trace.getTracer(name);\n}\n\nexport async function traceAsync<T>(\n name: string,\n fn: (span: Span) => Promise<T>,\n tracerName?: string\n): Promise<T> {\n const tracer = getTracer(tracerName);\n return tracer.startActiveSpan(name, async (span) => {\n try {\n const result = await fn(span);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n });\n}\n\nexport function traceSync<T>(\n name: string,\n fn: (span: Span) => T,\n tracerName?: string\n): T {\n const tracer = getTracer(tracerName);\n return tracer.startActiveSpan(name, (span) => {\n try {\n const result = fn(span);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n });\n}\n\n"],"mappings":"4EAUA,SAAgB,EAAU,EAAe,0BAA6B,CACpE,OAAO,EAAM,UAAU,EAAK,CAG9B,eAAsB,EACpB,EACA,EACA,EACY,CAEZ,OADe,EAAU,EAAW,CACtB,gBAAgB,EAAM,KAAO,IAAS,CAClD,GAAI,CACF,IAAM,EAAS,MAAM,EAAG,EAAK,CAE7B,OADA,EAAK,UAAU,CAAE,KAAM,EAAe,GAAI,CAAC,CACpC,QACA,EAAO,CAMd,MALA,EAAK,gBAAgB,EAAe,CACpC,EAAK,UAAU,CACb,KAAM,EAAe,MACrB,QAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAChE,CAAC,CACI,SACE,CACR,EAAK,KAAK,GAEZ,CAGJ,SAAgB,EACd,EACA,EACA,EACG,CAEH,OADe,EAAU,EAAW,CACtB,gBAAgB,EAAO,GAAS,CAC5C,GAAI,CACF,IAAM,EAAS,EAAG,EAAK,CAEvB,OADA,EAAK,UAAU,CAAE,KAAM,EAAe,GAAI,CAAC,CACpC,QACA,EAAO,CAMd,MALA,EAAK,gBAAgB,EAAe,CACpC,EAAK,UAAU,CACb,KAAM,EAAe,MACrB,QAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAChE,CAAC,CACI,SACE,CACR,EAAK,KAAK,GAEZ"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/tracing/index.ts"],"sourcesContent":["import {\n trace,\n context,\n SpanStatusCode,\n type Span,\n type Tracer,\n} from '@opentelemetry/api';\n\nconst DEFAULT_TRACER_NAME = '@lssm/lib.observability';\n\nexport function getTracer(name: string = DEFAULT_TRACER_NAME): Tracer {\n return trace.getTracer(name);\n}\n\nexport async function traceAsync<T>(\n name: string,\n fn: (span: Span) => Promise<T>,\n tracerName?: string\n): Promise<T> {\n const tracer = getTracer(tracerName);\n return tracer.startActiveSpan(name, async (span) => {\n try {\n const result = await fn(span);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n });\n}\n\nexport function traceSync<T>(\n name: string,\n fn: (span: Span) => T,\n tracerName?: string\n): T {\n const tracer = getTracer(tracerName);\n return tracer.startActiveSpan(name, (span) => {\n try {\n const result = fn(span);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error instanceof Error ? error.message : String(error),\n });\n throw error;\n } finally {\n span.end();\n }\n });\n}\n"],"mappings":"4EAUA,SAAgB,EAAU,EAAe,0BAA6B,CACpE,OAAO,EAAM,UAAU,EAAK,CAG9B,eAAsB,EACpB,EACA,EACA,EACY,CAEZ,OADe,EAAU,EAAW,CACtB,gBAAgB,EAAM,KAAO,IAAS,CAClD,GAAI,CACF,IAAM,EAAS,MAAM,EAAG,EAAK,CAE7B,OADA,EAAK,UAAU,CAAE,KAAM,EAAe,GAAI,CAAC,CACpC,QACA,EAAO,CAMd,MALA,EAAK,gBAAgB,EAAe,CACpC,EAAK,UAAU,CACb,KAAM,EAAe,MACrB,QAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAChE,CAAC,CACI,SACE,CACR,EAAK,KAAK,GAEZ,CAGJ,SAAgB,EACd,EACA,EACA,EACG,CAEH,OADe,EAAU,EAAW,CACtB,gBAAgB,EAAO,GAAS,CAC5C,GAAI,CACF,IAAM,EAAS,EAAG,EAAK,CAEvB,OADA,EAAK,UAAU,CAAE,KAAM,EAAe,GAAI,CAAC,CACpC,QACA,EAAO,CAMd,MALA,EAAK,gBAAgB,EAAe,CACpC,EAAK,UAAU,CACb,KAAM,EAAe,MACrB,QAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,EAAM,CAChE,CAAC,CACI,SACE,CACR,EAAK,KAAK,GAEZ"}
@@ -1,5 +1,19 @@
1
+ import { TelemetrySample } from "../intent/aggregator.mjs";
2
+
1
3
  //#region src/tracing/middleware.d.ts
2
- declare function createTracingMiddleware(): (req: Request, next: () => Promise<Response>) => Promise<Response>;
4
+ interface TracingMiddlewareOptions {
5
+ resolveOperation?: (input: {
6
+ req: Request;
7
+ res?: Response;
8
+ }) => {
9
+ name: string;
10
+ version: number;
11
+ } | undefined;
12
+ onSample?: (sample: TelemetrySample) => void;
13
+ tenantResolver?: (req: Request) => string | undefined;
14
+ actorResolver?: (req: Request) => string | undefined;
15
+ }
16
+ declare function createTracingMiddleware(options?: TracingMiddlewareOptions): (req: Request, next: () => Promise<Response>) => Promise<Response>;
3
17
  //#endregion
4
- export { createTracingMiddleware };
18
+ export { TracingMiddlewareOptions, createTracingMiddleware };
5
19
  //# sourceMappingURL=middleware.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.mts","names":[],"sources":["../../src/tracing/middleware.ts"],"sourcesContent":[],"mappings":";iBAGgB,uBAAA,CAAA,SACK,qBAAqB,QAAQ,cAAS,QAAA"}
1
+ {"version":3,"file":"middleware.d.mts","names":[],"sources":["../../src/tracing/middleware.ts"],"sourcesContent":[],"mappings":";;;UAKiB,wBAAA;;IAAA,GAAA,EAER,OAFQ;IAER,GAAA,CAAA,EACC,QADD;EACC,CAAA,EAAA,GAAA;IAEY,IAAA,EAAA,MAAA;IACG,OAAA,EAAA,MAAA;EACD,CAAA,GAAA,SAAA;EAAO,QAAA,CAAA,EAAA,CAAA,MAAA,EAFT,eAES,EAAA,GAAA,IAAA;EAGf,cAAA,CAAA,EAAA,CAAA,GAAA,EAJS,OAIc,EAAA,GAAA,MAAA,GAAA,SAAA;EAC5B,aAAA,CAAA,EAAA,CAAA,GAAA,EAJa,OAIb,EAAA,GAAA,MAAA,GAAA,SAAA;;AAEuC,iBAHlC,uBAAA,CAGkC,OAAA,CAAA,EAFvC,wBAEuC,CAAA,EAAA,CAAA,GAAA,EAA7B,OAA6B,EAAA,IAAA,EAAA,GAAA,GAAR,OAAQ,CAAA,QAAA,CAAA,EAAA,GAAS,OAAT,CAAS,QAAT,CAAA"}
@@ -1,2 +1,2 @@
1
- import{traceAsync as e}from"./index.mjs";import{standardMetrics as t}from"../metrics/index.mjs";function n(){return async(n,r)=>{let i=n.method,a=new URL(n.url).pathname;t.httpRequests.add(1,{method:i,path:a});let o=performance.now();return e(`HTTP ${i} ${a}`,async e=>{e.setAttribute(`http.method`,i),e.setAttribute(`http.url`,n.url);try{let n=await r();e.setAttribute(`http.status_code`,n.status);let s=(performance.now()-o)/1e3;return t.httpDuration.record(s,{method:i,path:a,status:n.status.toString()}),n}catch(e){throw t.operationErrors.add(1,{method:i,path:a}),e}})}}export{n as createTracingMiddleware};
1
+ import{traceAsync as e}from"./index.mjs";import{standardMetrics as t}from"../metrics/index.mjs";function n(n={}){return async(i,a)=>{let o=i.method,s=new URL(i.url).pathname;t.httpRequests.add(1,{method:o,path:s});let c=performance.now();return e(`HTTP ${o} ${s}`,async e=>{e.setAttribute(`http.method`,o),e.setAttribute(`http.url`,i.url);try{let l=await a();e.setAttribute(`http.status_code`,l.status);let u=(performance.now()-c)/1e3;return t.httpDuration.record(u,{method:o,path:s,status:l.status.toString()}),r({req:i,res:l,span:e,success:!0,durationMs:u*1e3,options:n}),l}catch(a){throw t.operationErrors.add(1,{method:o,path:s}),r({req:i,span:e,success:!1,durationMs:performance.now()-c,error:a,options:n}),a}})}}function r({req:e,res:t,span:n,success:r,durationMs:i,error:a,options:o}){if(!o.onSample||!o.resolveOperation)return;let s=o.resolveOperation({req:e,res:t});if(!s)return;let c={operation:s,durationMs:i,success:r,timestamp:new Date,errorCode:!r&&a instanceof Error?a.name:r?void 0:`unknown`,tenantId:o.tenantResolver?.(e),actorId:o.actorResolver?.(e),traceId:n.spanContext().traceId,metadata:{method:e.method,path:new URL(e.url).pathname,status:t?.status}};o.onSample(c)}export{n as createTracingMiddleware};
2
2
  //# sourceMappingURL=middleware.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.mjs","names":[],"sources":["../../src/tracing/middleware.ts"],"sourcesContent":["import { traceAsync } from './index';\nimport { standardMetrics } from '../metrics';\n\nexport function createTracingMiddleware() {\n return async (req: Request, next: () => Promise<Response>) => {\n const method = req.method;\n const url = new URL(req.url);\n const path = url.pathname;\n\n standardMetrics.httpRequests.add(1, { method, path });\n const startTime = performance.now();\n\n return traceAsync(`HTTP ${method} ${path}`, async (span) => {\n span.setAttribute('http.method', method);\n span.setAttribute('http.url', req.url);\n\n try {\n const response = await next();\n span.setAttribute('http.status_code', response.status);\n\n const duration = (performance.now() - startTime) / 1000;\n standardMetrics.httpDuration.record(duration, {\n method,\n path,\n status: response.status.toString(),\n });\n\n return response;\n } catch (error) {\n standardMetrics.operationErrors.add(1, { method, path });\n throw error;\n }\n });\n };\n}\n\n"],"mappings":"gGAGA,SAAgB,GAA0B,CACxC,OAAO,MAAO,EAAc,IAAkC,CAC5D,IAAM,EAAS,EAAI,OAEb,EADM,IAAI,IAAI,EAAI,IAAI,CACX,SAEjB,EAAgB,aAAa,IAAI,EAAG,CAAE,SAAQ,OAAM,CAAC,CACrD,IAAM,EAAY,YAAY,KAAK,CAEnC,OAAO,EAAW,QAAQ,EAAO,GAAG,IAAQ,KAAO,IAAS,CAC1D,EAAK,aAAa,cAAe,EAAO,CACxC,EAAK,aAAa,WAAY,EAAI,IAAI,CAEtC,GAAI,CACF,IAAM,EAAW,MAAM,GAAM,CAC7B,EAAK,aAAa,mBAAoB,EAAS,OAAO,CAEtD,IAAM,GAAY,YAAY,KAAK,CAAG,GAAa,IAOnD,OANA,EAAgB,aAAa,OAAO,EAAU,CAC5C,SACA,OACA,OAAQ,EAAS,OAAO,UAAU,CACnC,CAAC,CAEK,QACA,EAAO,CAEd,MADA,EAAgB,gBAAgB,IAAI,EAAG,CAAE,SAAQ,OAAM,CAAC,CAClD,IAER"}
1
+ {"version":3,"file":"middleware.mjs","names":["sample: TelemetrySample"],"sources":["../../src/tracing/middleware.ts"],"sourcesContent":["import type { Span } from '@opentelemetry/api';\nimport { traceAsync } from './index';\nimport { standardMetrics } from '../metrics';\nimport type { TelemetrySample } from '../intent/aggregator';\n\nexport interface TracingMiddlewareOptions {\n resolveOperation?: (input: {\n req: Request;\n res?: Response;\n }) => { name: string; version: number } | undefined;\n onSample?: (sample: TelemetrySample) => void;\n tenantResolver?: (req: Request) => string | undefined;\n actorResolver?: (req: Request) => string | undefined;\n}\n\nexport function createTracingMiddleware(\n options: TracingMiddlewareOptions = {}\n) {\n return async (req: Request, next: () => Promise<Response>) => {\n const method = req.method;\n const url = new URL(req.url);\n const path = url.pathname;\n\n standardMetrics.httpRequests.add(1, { method, path });\n const startTime = performance.now();\n\n return traceAsync(`HTTP ${method} ${path}`, async (span) => {\n span.setAttribute('http.method', method);\n span.setAttribute('http.url', req.url);\n\n try {\n const response = await next();\n span.setAttribute('http.status_code', response.status);\n\n const duration = (performance.now() - startTime) / 1000;\n standardMetrics.httpDuration.record(duration, {\n method,\n path,\n status: response.status.toString(),\n });\n\n emitTelemetrySample({\n req,\n res: response,\n span,\n success: true,\n durationMs: duration * 1000,\n options,\n });\n\n return response;\n } catch (error) {\n standardMetrics.operationErrors.add(1, { method, path });\n emitTelemetrySample({\n req,\n span,\n success: false,\n durationMs: performance.now() - startTime,\n error,\n options,\n });\n throw error;\n }\n });\n };\n}\n\ninterface EmitTelemetryArgs {\n req: Request;\n res?: Response;\n span: Span;\n success: boolean;\n durationMs: number;\n error?: unknown;\n options: TracingMiddlewareOptions;\n}\n\nfunction emitTelemetrySample({\n req,\n res,\n span,\n success,\n durationMs,\n error,\n options,\n}: EmitTelemetryArgs) {\n if (!options.onSample || !options.resolveOperation) return;\n const operation = options.resolveOperation({ req, res });\n if (!operation) return;\n const sample: TelemetrySample = {\n operation,\n durationMs,\n success,\n timestamp: new Date(),\n errorCode:\n !success && error instanceof Error\n ? error.name\n : success\n ? undefined\n : 'unknown',\n tenantId: options.tenantResolver?.(req),\n actorId: options.actorResolver?.(req),\n traceId: span.spanContext().traceId,\n metadata: {\n method: req.method,\n path: new URL(req.url).pathname,\n status: res?.status,\n },\n };\n options.onSample(sample);\n}\n"],"mappings":"gGAeA,SAAgB,EACd,EAAoC,EAAE,CACtC,CACA,OAAO,MAAO,EAAc,IAAkC,CAC5D,IAAM,EAAS,EAAI,OAEb,EADM,IAAI,IAAI,EAAI,IAAI,CACX,SAEjB,EAAgB,aAAa,IAAI,EAAG,CAAE,SAAQ,OAAM,CAAC,CACrD,IAAM,EAAY,YAAY,KAAK,CAEnC,OAAO,EAAW,QAAQ,EAAO,GAAG,IAAQ,KAAO,IAAS,CAC1D,EAAK,aAAa,cAAe,EAAO,CACxC,EAAK,aAAa,WAAY,EAAI,IAAI,CAEtC,GAAI,CACF,IAAM,EAAW,MAAM,GAAM,CAC7B,EAAK,aAAa,mBAAoB,EAAS,OAAO,CAEtD,IAAM,GAAY,YAAY,KAAK,CAAG,GAAa,IAgBnD,OAfA,EAAgB,aAAa,OAAO,EAAU,CAC5C,SACA,OACA,OAAQ,EAAS,OAAO,UAAU,CACnC,CAAC,CAEF,EAAoB,CAClB,MACA,IAAK,EACL,OACA,QAAS,GACT,WAAY,EAAW,IACvB,UACD,CAAC,CAEK,QACA,EAAO,CAUd,MATA,EAAgB,gBAAgB,IAAI,EAAG,CAAE,SAAQ,OAAM,CAAC,CACxD,EAAoB,CAClB,MACA,OACA,QAAS,GACT,WAAY,YAAY,KAAK,CAAG,EAChC,QACA,UACD,CAAC,CACI,IAER,EAcN,SAAS,EAAoB,CAC3B,MACA,MACA,OACA,UACA,aACA,QACA,WACoB,CACpB,GAAI,CAAC,EAAQ,UAAY,CAAC,EAAQ,iBAAkB,OACpD,IAAM,EAAY,EAAQ,iBAAiB,CAAE,MAAK,MAAK,CAAC,CACxD,GAAI,CAAC,EAAW,OAChB,IAAMA,EAA0B,CAC9B,YACA,aACA,UACA,UAAW,IAAI,KACf,UACE,CAAC,GAAW,aAAiB,MACzB,EAAM,KACN,EACE,IAAA,GACA,UACR,SAAU,EAAQ,iBAAiB,EAAI,CACvC,QAAS,EAAQ,gBAAgB,EAAI,CACrC,QAAS,EAAK,aAAa,CAAC,QAC5B,SAAU,CACR,OAAQ,EAAI,OACZ,KAAM,IAAI,IAAI,EAAI,IAAI,CAAC,SACvB,OAAQ,GAAK,OACd,CACF,CACD,EAAQ,SAAS,EAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lssm/lib.observability",
3
- "version": "0.0.0-canary-20251120192244",
3
+ "version": "0.2.0",
4
4
  "main": "./dist/index.mjs",
5
5
  "types": "./dist/index.d.mts",
6
6
  "scripts": {
@@ -14,6 +14,9 @@
14
14
  "lint:check": "eslint src",
15
15
  "test": "vitest run"
16
16
  },
17
+ "dependencies": {
18
+ "@lssm/lib.lifecycle": "workspace:*"
19
+ },
17
20
  "peerDependencies": {
18
21
  "@opentelemetry/api": "*"
19
22
  },
@@ -23,8 +26,16 @@
23
26
  },
24
27
  "exports": {
25
28
  ".": "./dist/index.mjs",
29
+ "./anomaly/alert-manager": "./dist/anomaly/alert-manager.mjs",
30
+ "./anomaly/anomaly-detector": "./dist/anomaly/anomaly-detector.mjs",
31
+ "./anomaly/baseline-calculator": "./dist/anomaly/baseline-calculator.mjs",
32
+ "./anomaly/root-cause-analyzer": "./dist/anomaly/root-cause-analyzer.mjs",
33
+ "./intent/aggregator": "./dist/intent/aggregator.mjs",
34
+ "./intent/detector": "./dist/intent/detector.mjs",
26
35
  "./logging": "./dist/logging/index.mjs",
27
36
  "./metrics": "./dist/metrics/index.mjs",
37
+ "./pipeline/evolution-pipeline": "./dist/pipeline/evolution-pipeline.mjs",
38
+ "./pipeline/lifecycle-pipeline": "./dist/pipeline/lifecycle-pipeline.mjs",
28
39
  "./tracing": "./dist/tracing/index.mjs",
29
40
  "./tracing/middleware": "./dist/tracing/middleware.mjs",
30
41
  "./*": "./*"
@@ -0,0 +1,31 @@
1
+ import type { AnomalySignal } from './anomaly-detector';
2
+ import type { RootCauseAnalysis } from './root-cause-analyzer';
3
+
4
+ export interface AlertManagerOptions {
5
+ cooldownMs?: number;
6
+ transport: (payload: {
7
+ signal: AnomalySignal;
8
+ analysis: RootCauseAnalysis;
9
+ }) => Promise<void> | void;
10
+ }
11
+
12
+ export class AlertManager {
13
+ private readonly cooldownMs: number;
14
+ private readonly lastAlert = new Map<string, number>();
15
+
16
+ constructor(private readonly options: AlertManagerOptions) {
17
+ this.cooldownMs = options.cooldownMs ?? 60_000;
18
+ }
19
+
20
+ async notify(signal: AnomalySignal, analysis: RootCauseAnalysis) {
21
+ const key = `${signal.type}:${analysis.culprit?.id ?? 'none'}`;
22
+ const now = Date.now();
23
+ const last = this.lastAlert.get(key) ?? 0;
24
+ if (now - last < this.cooldownMs) {
25
+ return;
26
+ }
27
+
28
+ await this.options.transport({ signal, analysis });
29
+ this.lastAlert.set(key, now);
30
+ }
31
+ }
@@ -0,0 +1,94 @@
1
+ import { BaselineCalculator, type MetricPoint } from './baseline-calculator';
2
+
3
+ export interface AnomalyThresholds {
4
+ errorRateDelta?: number;
5
+ latencyDelta?: number;
6
+ throughputDrop?: number;
7
+ minSamples?: number;
8
+ }
9
+
10
+ export interface AnomalySignal {
11
+ type: 'error_rate_spike' | 'latency_regression' | 'throughput_drop';
12
+ delta: number;
13
+ point: MetricPoint;
14
+ baseline: ReturnType<BaselineCalculator['getSnapshot']>;
15
+ }
16
+
17
+ export class AnomalyDetector {
18
+ private readonly baseline: BaselineCalculator;
19
+ private readonly thresholds: Required<AnomalyThresholds> = {
20
+ errorRateDelta: 0.5,
21
+ latencyDelta: 0.35,
22
+ throughputDrop: 0.4,
23
+ minSamples: 10,
24
+ } as Required<AnomalyThresholds>;
25
+
26
+ constructor(options: AnomalyThresholds = {}) {
27
+ this.baseline = new BaselineCalculator();
28
+ this.thresholds = { ...this.thresholds, ...options };
29
+ }
30
+
31
+ evaluate(point: MetricPoint): AnomalySignal[] {
32
+ const baselineSnapshot = this.baseline.update(point);
33
+ if (baselineSnapshot.sampleCount < this.thresholds.minSamples) {
34
+ return [];
35
+ }
36
+
37
+ const signals: AnomalySignal[] = [];
38
+
39
+ const errorDelta = this.relativeDelta(
40
+ point.errorRate,
41
+ baselineSnapshot.errorRate
42
+ );
43
+ if (errorDelta > this.thresholds.errorRateDelta) {
44
+ signals.push({
45
+ type: 'error_rate_spike',
46
+ delta: errorDelta,
47
+ point,
48
+ baseline: baselineSnapshot,
49
+ });
50
+ }
51
+
52
+ const latencyDelta = this.relativeDelta(
53
+ point.latencyP99,
54
+ baselineSnapshot.latencyP99
55
+ );
56
+ if (latencyDelta > this.thresholds.latencyDelta) {
57
+ signals.push({
58
+ type: 'latency_regression',
59
+ delta: latencyDelta,
60
+ point,
61
+ baseline: baselineSnapshot,
62
+ });
63
+ }
64
+
65
+ const throughputDelta = this.relativeDrop(
66
+ point.throughput,
67
+ baselineSnapshot.throughput
68
+ );
69
+ if (throughputDelta > this.thresholds.throughputDrop) {
70
+ signals.push({
71
+ type: 'throughput_drop',
72
+ delta: throughputDelta,
73
+ point,
74
+ baseline: baselineSnapshot,
75
+ });
76
+ }
77
+
78
+ return signals;
79
+ }
80
+
81
+ private relativeDelta(value: number, baseline: number) {
82
+ if (baseline === 0) {
83
+ return 0;
84
+ }
85
+ return (value - baseline) / baseline;
86
+ }
87
+
88
+ private relativeDrop(value: number, baseline: number) {
89
+ if (baseline === 0) {
90
+ return 0;
91
+ }
92
+ return (baseline - value) / baseline;
93
+ }
94
+ }
@@ -0,0 +1,54 @@
1
+ export interface MetricPoint {
2
+ latencyP99: number;
3
+ latencyP95: number;
4
+ errorRate: number;
5
+ throughput: number;
6
+ timestamp: Date;
7
+ }
8
+
9
+ export interface BaselineSnapshot {
10
+ latencyP99: number;
11
+ latencyP95: number;
12
+ errorRate: number;
13
+ throughput: number;
14
+ sampleCount: number;
15
+ }
16
+
17
+ export class BaselineCalculator {
18
+ private snapshot: BaselineSnapshot = {
19
+ latencyP99: 0,
20
+ latencyP95: 0,
21
+ errorRate: 0,
22
+ throughput: 0,
23
+ sampleCount: 0,
24
+ };
25
+
26
+ constructor(private readonly alpha = 0.2) {}
27
+
28
+ update(point: MetricPoint): BaselineSnapshot {
29
+ const { sampleCount } = this.snapshot;
30
+ const nextCount = sampleCount + 1;
31
+ const weight = sampleCount === 0 ? 1 : this.alpha;
32
+
33
+ this.snapshot = {
34
+ latencyP99: this.mix(this.snapshot.latencyP99, point.latencyP99, weight),
35
+ latencyP95: this.mix(this.snapshot.latencyP95, point.latencyP95, weight),
36
+ errorRate: this.mix(this.snapshot.errorRate, point.errorRate, weight),
37
+ throughput: this.mix(this.snapshot.throughput, point.throughput, weight),
38
+ sampleCount: nextCount,
39
+ };
40
+
41
+ return this.snapshot;
42
+ }
43
+
44
+ getSnapshot() {
45
+ return this.snapshot;
46
+ }
47
+
48
+ private mix(current: number, next: number, weight: number) {
49
+ if (this.snapshot.sampleCount === 0) {
50
+ return next;
51
+ }
52
+ return current * (1 - weight) + next * weight;
53
+ }
54
+ }
@@ -0,0 +1,55 @@
1
+ import type { AnomalySignal } from './anomaly-detector';
2
+
3
+ export interface DeploymentEvent {
4
+ id: string;
5
+ operation: string;
6
+ deployedAt: Date;
7
+ stage?: string;
8
+ status: 'in_progress' | 'completed' | 'rolled_back';
9
+ }
10
+
11
+ export interface RootCauseAnalysis {
12
+ signal: AnomalySignal;
13
+ culprit?: DeploymentEvent;
14
+ notes: string[];
15
+ }
16
+
17
+ export class RootCauseAnalyzer {
18
+ constructor(private readonly lookbackMs: number = 15 * 60 * 1000) {}
19
+
20
+ analyze(
21
+ signal: AnomalySignal,
22
+ deployments: DeploymentEvent[]
23
+ ): RootCauseAnalysis {
24
+ const windowStart = new Date(
25
+ signal.point.timestamp.getTime() - this.lookbackMs
26
+ );
27
+ const candidates = deployments
28
+ .filter((deployment) => deployment.deployedAt >= windowStart)
29
+ .sort((a, b) => b.deployedAt.getTime() - a.deployedAt.getTime());
30
+
31
+ const notes: string[] = [];
32
+ let culprit: DeploymentEvent | undefined;
33
+
34
+ if (candidates.length > 0) {
35
+ culprit = candidates[0]!;
36
+ notes.push(
37
+ `Closest deployment ${culprit.id} (${culprit.operation}) at ${culprit.deployedAt.toISOString()}`
38
+ );
39
+ } else {
40
+ notes.push('No deployments found within lookback window.');
41
+ }
42
+
43
+ if (signal.type === 'latency_regression') {
44
+ notes.push(
45
+ 'Verify recent schema changes and external dependency latency.'
46
+ );
47
+ }
48
+
49
+ if (signal.type === 'error_rate_spike') {
50
+ notes.push('Check SLO monitor for correlated incidents.');
51
+ }
52
+
53
+ return { signal, culprit, notes };
54
+ }
55
+ }
package/src/index.ts CHANGED
@@ -7,7 +7,41 @@ export {
7
7
  standardMetrics,
8
8
  } from './metrics';
9
9
  export { Logger, logger } from './logging';
10
- export { createTracingMiddleware } from './tracing/middleware';
10
+ export {
11
+ createTracingMiddleware,
12
+ type TracingMiddlewareOptions,
13
+ } from './tracing/middleware';
14
+ export {
15
+ IntentAggregator,
16
+ type IntentAggregatorSnapshot,
17
+ type TelemetrySample,
18
+ } from './intent/aggregator';
19
+ export {
20
+ IntentDetector,
21
+ type IntentSignal,
22
+ type IntentSignalType,
23
+ } from './intent/detector';
24
+ export {
25
+ EvolutionPipeline,
26
+ type EvolutionPipelineEvent,
27
+ type EvolutionPipelineOptions,
28
+ } from './pipeline/evolution-pipeline';
29
+ export {
30
+ LifecycleKpiPipeline,
31
+ type LifecycleKpiPipelineOptions,
32
+ type LifecyclePipelineEvent,
33
+ } from './pipeline/lifecycle-pipeline';
11
34
 
12
35
  export type { LogLevel, LogEntry } from './logging';
13
36
 
37
+ export { BaselineCalculator } from './anomaly/baseline-calculator';
38
+ export {
39
+ AnomalyDetector,
40
+ type AnomalySignal,
41
+ type AnomalyThresholds,
42
+ } from './anomaly/anomaly-detector';
43
+ export {
44
+ RootCauseAnalyzer,
45
+ type RootCauseAnalysis,
46
+ } from './anomaly/root-cause-analyzer';
47
+ export { AlertManager } from './anomaly/alert-manager';