@lssm/lib.observability 1.42.0 → 1.42.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.
package/CHANGELOG.md CHANGED
@@ -1,14 +1,30 @@
1
1
  # @lssm/lib.observability
2
2
 
3
+ ## 1.42.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 1f9ac4c: fix
8
+ - Updated dependencies [1f9ac4c]
9
+ - @lssm/lib.lifecycle@1.42.2
10
+
11
+ ## 1.42.1
12
+
13
+ ### Patch Changes
14
+
15
+ - f043995: Fix release
16
+ - Updated dependencies [f043995]
17
+ - @lssm/lib.lifecycle@1.42.1
18
+
3
19
  ## 1.42.0
4
20
 
5
21
  ### Minor Changes
6
22
 
7
- - 66a5dfd: initial release
23
+ - 8eefd9c: initial release
8
24
 
9
25
  ### Patch Changes
10
26
 
11
- - Updated dependencies [66a5dfd]
27
+ - Updated dependencies [8eefd9c]
12
28
  - @lssm/lib.lifecycle@1.42.0
13
29
 
14
30
  ## 0.5.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Chaman Ventures, SASU
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @lssm/lib.observability
2
2
 
3
+ Website: https://contractspec.lssm.tech/
4
+
5
+
3
6
  OpenTelemetry integration for tracing, metrics, and structured logging.
4
7
 
5
8
  ## Features
@@ -60,6 +63,16 @@ Full docs: https://contractspec.lssm.tech/docs/libraries/observability
60
63
 
61
64
 
62
65
 
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
63
76
 
64
77
 
65
78
 
@@ -0,0 +1,21 @@
1
+ import { AnomalySignal } from "./anomaly-detector.mjs";
2
+ import { RootCauseAnalysis } from "./root-cause-analyzer.mjs";
3
+
4
+ //#region src/anomaly/alert-manager.d.ts
5
+ interface AlertManagerOptions {
6
+ cooldownMs?: number;
7
+ transport: (payload: {
8
+ signal: AnomalySignal;
9
+ analysis: RootCauseAnalysis;
10
+ }) => Promise<void> | void;
11
+ }
12
+ declare class AlertManager {
13
+ private readonly options;
14
+ private readonly cooldownMs;
15
+ private readonly lastAlert;
16
+ constructor(options: AlertManagerOptions);
17
+ notify(signal: AnomalySignal, analysis: RootCauseAnalysis): Promise<void>;
18
+ }
19
+ //#endregion
20
+ export { AlertManager, AlertManagerOptions };
21
+ //# sourceMappingURL=alert-manager.d.mts.map
@@ -1 +1,23 @@
1
- var e=class{cooldownMs;lastAlert=new Map;constructor(e){this.options=e,this.cooldownMs=e.cooldownMs??6e4}async notify(e,t){let n=`${e.type}:${t.culprit?.id??`none`}`,r=Date.now();r-(this.lastAlert.get(n)??0)<this.cooldownMs||(await this.options.transport({signal:e,analysis:t}),this.lastAlert.set(n,r))}};export{e as AlertManager};
1
+ //#region src/anomaly/alert-manager.ts
2
+ var AlertManager = class {
3
+ cooldownMs;
4
+ lastAlert = /* @__PURE__ */ new Map();
5
+ constructor(options) {
6
+ this.options = options;
7
+ this.cooldownMs = options.cooldownMs ?? 6e4;
8
+ }
9
+ async notify(signal, analysis) {
10
+ const key = `${signal.type}:${analysis.culprit?.id ?? "none"}`;
11
+ const now = Date.now();
12
+ if (now - (this.lastAlert.get(key) ?? 0) < this.cooldownMs) return;
13
+ await this.options.transport({
14
+ signal,
15
+ analysis
16
+ });
17
+ this.lastAlert.set(key, now);
18
+ }
19
+ };
20
+
21
+ //#endregion
22
+ export { AlertManager };
23
+ //# sourceMappingURL=alert-manager.mjs.map
@@ -0,0 +1,26 @@
1
+ import { BaselineCalculator, MetricPoint } from "./baseline-calculator.mjs";
2
+
3
+ //#region src/anomaly/anomaly-detector.d.ts
4
+ interface AnomalyThresholds {
5
+ errorRateDelta?: number;
6
+ latencyDelta?: number;
7
+ throughputDrop?: number;
8
+ minSamples?: number;
9
+ }
10
+ interface AnomalySignal {
11
+ type: 'error_rate_spike' | 'latency_regression' | 'throughput_drop';
12
+ delta: number;
13
+ point: MetricPoint;
14
+ baseline: ReturnType<BaselineCalculator['getSnapshot']>;
15
+ }
16
+ declare class AnomalyDetector {
17
+ private readonly baseline;
18
+ private readonly thresholds;
19
+ constructor(options?: AnomalyThresholds);
20
+ evaluate(point: MetricPoint): AnomalySignal[];
21
+ private relativeDelta;
22
+ private relativeDrop;
23
+ }
24
+ //#endregion
25
+ export { AnomalyDetector, AnomalySignal, AnomalyThresholds };
26
+ //# sourceMappingURL=anomaly-detector.d.mts.map
@@ -1 +1,58 @@
1
- import{BaselineCalculator as e}from"./baseline-calculator.mjs";var t=class{baseline;thresholds={errorRateDelta:.5,latencyDelta:.35,throughputDrop:.4,minSamples:10};constructor(t={}){this.baseline=new e,this.thresholds={...this.thresholds,...t}}evaluate(e){let t=this.baseline.update(e);if(t.sampleCount<this.thresholds.minSamples)return[];let n=[],r=this.relativeDelta(e.errorRate,t.errorRate);r>this.thresholds.errorRateDelta&&n.push({type:`error_rate_spike`,delta:r,point:e,baseline:t});let i=this.relativeDelta(e.latencyP99,t.latencyP99);i>this.thresholds.latencyDelta&&n.push({type:`latency_regression`,delta:i,point:e,baseline:t});let a=this.relativeDrop(e.throughput,t.throughput);return a>this.thresholds.throughputDrop&&n.push({type:`throughput_drop`,delta:a,point:e,baseline:t}),n}relativeDelta(e,t){return t===0?0:(e-t)/t}relativeDrop(e,t){return t===0?0:(t-e)/t}};export{t as AnomalyDetector};
1
+ import { BaselineCalculator } from "./baseline-calculator.mjs";
2
+
3
+ //#region src/anomaly/anomaly-detector.ts
4
+ var AnomalyDetector = class {
5
+ baseline;
6
+ thresholds = {
7
+ errorRateDelta: .5,
8
+ latencyDelta: .35,
9
+ throughputDrop: .4,
10
+ minSamples: 10
11
+ };
12
+ constructor(options = {}) {
13
+ this.baseline = new BaselineCalculator();
14
+ this.thresholds = {
15
+ ...this.thresholds,
16
+ ...options
17
+ };
18
+ }
19
+ evaluate(point) {
20
+ const baselineSnapshot = this.baseline.update(point);
21
+ if (baselineSnapshot.sampleCount < this.thresholds.minSamples) return [];
22
+ const signals = [];
23
+ const errorDelta = this.relativeDelta(point.errorRate, baselineSnapshot.errorRate);
24
+ if (errorDelta > this.thresholds.errorRateDelta) signals.push({
25
+ type: "error_rate_spike",
26
+ delta: errorDelta,
27
+ point,
28
+ baseline: baselineSnapshot
29
+ });
30
+ const latencyDelta = this.relativeDelta(point.latencyP99, baselineSnapshot.latencyP99);
31
+ if (latencyDelta > this.thresholds.latencyDelta) signals.push({
32
+ type: "latency_regression",
33
+ delta: latencyDelta,
34
+ point,
35
+ baseline: baselineSnapshot
36
+ });
37
+ const throughputDelta = this.relativeDrop(point.throughput, baselineSnapshot.throughput);
38
+ if (throughputDelta > this.thresholds.throughputDrop) signals.push({
39
+ type: "throughput_drop",
40
+ delta: throughputDelta,
41
+ point,
42
+ baseline: baselineSnapshot
43
+ });
44
+ return signals;
45
+ }
46
+ relativeDelta(value, baseline) {
47
+ if (baseline === 0) return 0;
48
+ return (value - baseline) / baseline;
49
+ }
50
+ relativeDrop(value, baseline) {
51
+ if (baseline === 0) return 0;
52
+ return (baseline - value) / baseline;
53
+ }
54
+ };
55
+
56
+ //#endregion
57
+ export { AnomalyDetector };
58
+ //# sourceMappingURL=anomaly-detector.mjs.map
@@ -0,0 +1,26 @@
1
+ //#region src/anomaly/baseline-calculator.d.ts
2
+ interface MetricPoint {
3
+ latencyP99: number;
4
+ latencyP95: number;
5
+ errorRate: number;
6
+ throughput: number;
7
+ timestamp: Date;
8
+ }
9
+ interface BaselineSnapshot {
10
+ latencyP99: number;
11
+ latencyP95: number;
12
+ errorRate: number;
13
+ throughput: number;
14
+ sampleCount: number;
15
+ }
16
+ declare class BaselineCalculator {
17
+ private readonly alpha;
18
+ private snapshot;
19
+ constructor(alpha?: number);
20
+ update(point: MetricPoint): BaselineSnapshot;
21
+ getSnapshot(): BaselineSnapshot;
22
+ private mix;
23
+ }
24
+ //#endregion
25
+ export { BaselineCalculator, BaselineSnapshot, MetricPoint };
26
+ //# sourceMappingURL=baseline-calculator.d.mts.map
@@ -1 +1,37 @@
1
- var e=class{snapshot={latencyP99:0,latencyP95:0,errorRate:0,throughput:0,sampleCount:0};constructor(e=.2){this.alpha=e}update(e){let{sampleCount:t}=this.snapshot,n=t+1,r=t===0?1:this.alpha;return this.snapshot={latencyP99:this.mix(this.snapshot.latencyP99,e.latencyP99,r),latencyP95:this.mix(this.snapshot.latencyP95,e.latencyP95,r),errorRate:this.mix(this.snapshot.errorRate,e.errorRate,r),throughput:this.mix(this.snapshot.throughput,e.throughput,r),sampleCount:n},this.snapshot}getSnapshot(){return this.snapshot}mix(e,t,n){return this.snapshot.sampleCount===0?t:e*(1-n)+t*n}};export{e as BaselineCalculator};
1
+ //#region src/anomaly/baseline-calculator.ts
2
+ var BaselineCalculator = class {
3
+ snapshot = {
4
+ latencyP99: 0,
5
+ latencyP95: 0,
6
+ errorRate: 0,
7
+ throughput: 0,
8
+ sampleCount: 0
9
+ };
10
+ constructor(alpha = .2) {
11
+ this.alpha = alpha;
12
+ }
13
+ update(point) {
14
+ const { sampleCount } = this.snapshot;
15
+ const nextCount = sampleCount + 1;
16
+ const weight = sampleCount === 0 ? 1 : this.alpha;
17
+ this.snapshot = {
18
+ latencyP99: this.mix(this.snapshot.latencyP99, point.latencyP99, weight),
19
+ latencyP95: this.mix(this.snapshot.latencyP95, point.latencyP95, weight),
20
+ errorRate: this.mix(this.snapshot.errorRate, point.errorRate, weight),
21
+ throughput: this.mix(this.snapshot.throughput, point.throughput, weight),
22
+ sampleCount: nextCount
23
+ };
24
+ return this.snapshot;
25
+ }
26
+ getSnapshot() {
27
+ return this.snapshot;
28
+ }
29
+ mix(current, next, weight) {
30
+ if (this.snapshot.sampleCount === 0) return next;
31
+ return current * (1 - weight) + next * weight;
32
+ }
33
+ };
34
+
35
+ //#endregion
36
+ export { BaselineCalculator };
37
+ //# sourceMappingURL=baseline-calculator.mjs.map
@@ -0,0 +1,23 @@
1
+ import { AnomalySignal } from "./anomaly-detector.mjs";
2
+
3
+ //#region src/anomaly/root-cause-analyzer.d.ts
4
+ interface DeploymentEvent {
5
+ id: string;
6
+ operation: string;
7
+ deployedAt: Date;
8
+ stage?: string;
9
+ status: 'in_progress' | 'completed' | 'rolled_back';
10
+ }
11
+ interface RootCauseAnalysis {
12
+ signal: AnomalySignal;
13
+ culprit?: DeploymentEvent;
14
+ notes: string[];
15
+ }
16
+ declare class RootCauseAnalyzer {
17
+ private readonly lookbackMs;
18
+ constructor(lookbackMs?: number);
19
+ analyze(signal: AnomalySignal, deployments: DeploymentEvent[]): RootCauseAnalysis;
20
+ }
21
+ //#endregion
22
+ export { DeploymentEvent, RootCauseAnalysis, RootCauseAnalyzer };
23
+ //# sourceMappingURL=root-cause-analyzer.d.mts.map
@@ -1 +1,27 @@
1
- var e=class{constructor(e=900*1e3){this.lookbackMs=e}analyze(e,t){let n=new Date(e.point.timestamp.getTime()-this.lookbackMs),r=t.filter(e=>e.deployedAt>=n).sort((e,t)=>t.deployedAt.getTime()-e.deployedAt.getTime()),i=[],a;return r.length>0?(a=r[0],i.push(`Closest deployment ${a.id} (${a.operation}) at ${a.deployedAt.toISOString()}`)):i.push(`No deployments found within lookback window.`),e.type===`latency_regression`&&i.push(`Verify recent schema changes and external dependency latency.`),e.type===`error_rate_spike`&&i.push(`Check SLO monitor for correlated incidents.`),{signal:e,culprit:a,notes:i}}};export{e as RootCauseAnalyzer};
1
+ //#region src/anomaly/root-cause-analyzer.ts
2
+ var RootCauseAnalyzer = class {
3
+ constructor(lookbackMs = 900 * 1e3) {
4
+ this.lookbackMs = lookbackMs;
5
+ }
6
+ analyze(signal, deployments) {
7
+ const windowStart = new Date(signal.point.timestamp.getTime() - this.lookbackMs);
8
+ const candidates = deployments.filter((deployment) => deployment.deployedAt >= windowStart).sort((a, b) => b.deployedAt.getTime() - a.deployedAt.getTime());
9
+ const notes = [];
10
+ let culprit;
11
+ if (candidates.length > 0) {
12
+ culprit = candidates[0];
13
+ if (culprit) notes.push(`Closest deployment ${culprit.id} (${culprit.operation}) at ${culprit.deployedAt.toISOString()}`);
14
+ } else notes.push("No deployments found within lookback window.");
15
+ if (signal.type === "latency_regression") notes.push("Verify recent schema changes and external dependency latency.");
16
+ if (signal.type === "error_rate_spike") notes.push("Check SLO monitor for correlated incidents.");
17
+ return {
18
+ signal,
19
+ culprit,
20
+ notes
21
+ };
22
+ }
23
+ };
24
+
25
+ //#endregion
26
+ export { RootCauseAnalyzer };
27
+ //# sourceMappingURL=root-cause-analyzer.mjs.map
@@ -0,0 +1,13 @@
1
+ import { BaselineCalculator } from "./anomaly/baseline-calculator.mjs";
2
+ import { AnomalyDetector, AnomalySignal, AnomalyThresholds } from "./anomaly/anomaly-detector.mjs";
3
+ import { RootCauseAnalysis, RootCauseAnalyzer } from "./anomaly/root-cause-analyzer.mjs";
4
+ import { AlertManager } from "./anomaly/alert-manager.mjs";
5
+ import { getTracer, traceAsync, traceSync } from "./tracing/index.mjs";
6
+ import { createCounter, createHistogram, createUpDownCounter, getMeter, standardMetrics } from "./metrics/index.mjs";
7
+ import { LogEntry, LogLevel, Logger, logger } from "./logging/index.mjs";
8
+ import { IntentAggregator, IntentAggregatorSnapshot, TelemetrySample } from "./intent/aggregator.mjs";
9
+ import { TracingMiddlewareOptions, createTracingMiddleware } from "./tracing/middleware.mjs";
10
+ import { IntentDetector, IntentSignal, IntentSignalType } from "./intent/detector.mjs";
11
+ import { EvolutionPipeline, EvolutionPipelineEvent, EvolutionPipelineOptions } from "./pipeline/evolution-pipeline.mjs";
12
+ import { LifecycleKpiPipeline, LifecycleKpiPipelineOptions, LifecyclePipelineEvent } from "./pipeline/lifecycle-pipeline.mjs";
13
+ export { AlertManager, AnomalyDetector, type AnomalySignal, type AnomalyThresholds, BaselineCalculator, EvolutionPipeline, type EvolutionPipelineEvent, type EvolutionPipelineOptions, IntentAggregator, type IntentAggregatorSnapshot, IntentDetector, type IntentSignal, type IntentSignalType, LifecycleKpiPipeline, type LifecycleKpiPipelineOptions, type LifecyclePipelineEvent, type LogEntry, type LogLevel, Logger, type RootCauseAnalysis, RootCauseAnalyzer, type TelemetrySample, type TracingMiddlewareOptions, createCounter, createHistogram, createTracingMiddleware, createUpDownCounter, getMeter, getTracer, logger, standardMetrics, traceAsync, traceSync };
package/dist/index.mjs CHANGED
@@ -1 +1,14 @@
1
- import{getTracer as e,traceAsync as t,traceSync as n}from"./tracing/index.mjs";import{createCounter as r,createHistogram as i,createUpDownCounter as a,getMeter as o,standardMetrics as s}from"./metrics/index.mjs";import{Logger as c,logger as l}from"./logging/index.mjs";import{createTracingMiddleware as u}from"./tracing/middleware.mjs";import{IntentAggregator as d}from"./intent/aggregator.mjs";import{IntentDetector as f}from"./intent/detector.mjs";import{EvolutionPipeline as p}from"./pipeline/evolution-pipeline.mjs";import{LifecycleKpiPipeline as m}from"./pipeline/lifecycle-pipeline.mjs";import{BaselineCalculator as h}from"./anomaly/baseline-calculator.mjs";import{AnomalyDetector as g}from"./anomaly/anomaly-detector.mjs";import{RootCauseAnalyzer as _}from"./anomaly/root-cause-analyzer.mjs";import{AlertManager as v}from"./anomaly/alert-manager.mjs";export{v as AlertManager,g as AnomalyDetector,h as BaselineCalculator,p as EvolutionPipeline,d as IntentAggregator,f as IntentDetector,m as LifecycleKpiPipeline,c as Logger,_ as RootCauseAnalyzer,r as createCounter,i as createHistogram,u as createTracingMiddleware,a as createUpDownCounter,o as getMeter,e as getTracer,l as logger,s as standardMetrics,t as traceAsync,n as traceSync};
1
+ import { getTracer, traceAsync, traceSync } from "./tracing/index.mjs";
2
+ import { createCounter, createHistogram, createUpDownCounter, getMeter, standardMetrics } from "./metrics/index.mjs";
3
+ import { Logger, logger } from "./logging/index.mjs";
4
+ import { createTracingMiddleware } from "./tracing/middleware.mjs";
5
+ import { IntentAggregator } from "./intent/aggregator.mjs";
6
+ import { IntentDetector } from "./intent/detector.mjs";
7
+ import { EvolutionPipeline } from "./pipeline/evolution-pipeline.mjs";
8
+ import { LifecycleKpiPipeline } from "./pipeline/lifecycle-pipeline.mjs";
9
+ import { BaselineCalculator } from "./anomaly/baseline-calculator.mjs";
10
+ import { AnomalyDetector } from "./anomaly/anomaly-detector.mjs";
11
+ import { RootCauseAnalyzer } from "./anomaly/root-cause-analyzer.mjs";
12
+ import { AlertManager } from "./anomaly/alert-manager.mjs";
13
+
14
+ export { AlertManager, AnomalyDetector, BaselineCalculator, EvolutionPipeline, IntentAggregator, IntentDetector, LifecycleKpiPipeline, Logger, RootCauseAnalyzer, createCounter, createHistogram, createTracingMiddleware, createUpDownCounter, getMeter, getTracer, logger, standardMetrics, traceAsync, traceSync };
@@ -0,0 +1,60 @@
1
+ //#region src/intent/aggregator.d.ts
2
+ interface TelemetrySample {
3
+ operation: {
4
+ name: string;
5
+ version: number;
6
+ };
7
+ durationMs: number;
8
+ success: boolean;
9
+ timestamp: Date;
10
+ errorCode?: string;
11
+ tenantId?: string;
12
+ traceId?: string;
13
+ actorId?: string;
14
+ metadata?: Record<string, unknown>;
15
+ }
16
+ interface AggregatedOperationMetrics {
17
+ operation: {
18
+ name: string;
19
+ version: number;
20
+ };
21
+ totalCalls: number;
22
+ successRate: number;
23
+ errorRate: number;
24
+ averageLatencyMs: number;
25
+ p95LatencyMs: number;
26
+ p99LatencyMs: number;
27
+ maxLatencyMs: number;
28
+ windowStart: Date;
29
+ windowEnd: Date;
30
+ topErrors: Record<string, number>;
31
+ }
32
+ interface OperationSequence {
33
+ steps: string[];
34
+ tenantId?: string;
35
+ count: number;
36
+ }
37
+ interface IntentAggregatorSnapshot {
38
+ metrics: AggregatedOperationMetrics[];
39
+ sequences: OperationSequence[];
40
+ sampleCount: number;
41
+ windowStart?: Date;
42
+ windowEnd?: Date;
43
+ }
44
+ interface IntentAggregatorOptions {
45
+ windowMs?: number;
46
+ sequenceSampleSize?: number;
47
+ }
48
+ declare class IntentAggregator {
49
+ private readonly windowMs;
50
+ private readonly sequenceSampleSize;
51
+ private readonly samples;
52
+ constructor(options?: IntentAggregatorOptions);
53
+ add(sample: TelemetrySample): void;
54
+ flush(now?: Date): IntentAggregatorSnapshot;
55
+ private aggregateMetrics;
56
+ private buildSequences;
57
+ }
58
+ //#endregion
59
+ export { AggregatedOperationMetrics, IntentAggregator, IntentAggregatorOptions, IntentAggregatorSnapshot, OperationSequence, TelemetrySample };
60
+ //# sourceMappingURL=aggregator.d.mts.map
@@ -1 +1,98 @@
1
- var e=class{windowMs;sequenceSampleSize;samples=[];constructor(e={}){this.windowMs=e.windowMs??9e5,this.sequenceSampleSize=e.sequenceSampleSize??1e3}add(e){this.samples.push(e)}flush(e=new Date){let t=e.getTime()-this.windowMs,n=this.samples.filter(e=>e.timestamp.getTime()>=t);this.samples.length=0;let r=this.aggregateMetrics(n),i=this.buildSequences(n),a=n.map(e=>e.timestamp.getTime());return{metrics:r,sequences:i,sampleCount:n.length,windowStart:a.length?new Date(Math.min(...a)):void 0,windowEnd:a.length?new Date(Math.max(...a)):void 0}}aggregateMetrics(e){if(!e.length)return[];let n=new Map;for(let t of e){let e=`${t.operation.name}.v${t.operation.version}`,r=n.get(e)??[];r.push(t),n.set(e,r)}return[...n.values()].map(e=>{let n=e.map(e=>e.durationMs).sort((e,t)=>e-t),r=e.filter(e=>!e.success),i=e.length,a=r.reduce((e,t)=>(t.errorCode&&(e[t.errorCode]=(e[t.errorCode]??0)+1),e),{}),o=e.map(e=>e.timestamp.getTime());return{operation:e[0].operation,totalCalls:i,successRate:(i-r.length)/i,errorRate:r.length/i,averageLatencyMs:n.reduce((e,t)=>e+t,0)/i,p95LatencyMs:t(n,.95),p99LatencyMs:t(n,.99),maxLatencyMs:Math.max(...n),windowStart:new Date(Math.min(...o)),windowEnd:new Date(Math.max(...o)),topErrors:a}})}buildSequences(e){let t=new Map;for(let n of e.slice(-this.sequenceSampleSize)){if(!n.traceId)continue;let e=t.get(n.traceId)??[];e.push(n),t.set(n.traceId,e)}let n={};for(let[e,r]of t.entries()){let e=r.sort((e,t)=>e.timestamp.getTime()-t.timestamp.getTime()),t=e.map(e=>e.operation.name);if(t.length<2)continue;let i=`${t.join(`>`)}@${e[0]?.tenantId??`global`}`,a=n[i];a?a.count+=1:n[i]={steps:t,tenantId:e[0]?.tenantId,count:1}}return Object.values(n).sort((e,t)=>t.count-e.count)}};function t(e,t){return e.length?e.length===1?e[0]:e[Math.min(e.length-1,Math.floor(t*e.length))]:0}export{e as IntentAggregator};
1
+ //#region src/intent/aggregator.ts
2
+ const DEFAULT_WINDOW_MS = 900 * 1e3;
3
+ var IntentAggregator = class {
4
+ windowMs;
5
+ sequenceSampleSize;
6
+ samples = [];
7
+ constructor(options = {}) {
8
+ this.windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
9
+ this.sequenceSampleSize = options.sequenceSampleSize ?? 1e3;
10
+ }
11
+ add(sample) {
12
+ this.samples.push(sample);
13
+ }
14
+ flush(now = /* @__PURE__ */ new Date()) {
15
+ const minTimestamp = now.getTime() - this.windowMs;
16
+ const windowSamples = this.samples.filter((sample) => sample.timestamp.getTime() >= minTimestamp);
17
+ this.samples.length = 0;
18
+ const metrics = this.aggregateMetrics(windowSamples);
19
+ const sequences = this.buildSequences(windowSamples);
20
+ const timestamps = windowSamples.map((sample) => sample.timestamp.getTime());
21
+ return {
22
+ metrics,
23
+ sequences,
24
+ sampleCount: windowSamples.length,
25
+ windowStart: timestamps.length ? new Date(Math.min(...timestamps)) : void 0,
26
+ windowEnd: timestamps.length ? new Date(Math.max(...timestamps)) : void 0
27
+ };
28
+ }
29
+ aggregateMetrics(samples) {
30
+ if (!samples.length) return [];
31
+ const groups = /* @__PURE__ */ new Map();
32
+ for (const sample of samples) {
33
+ const key = `${sample.operation.name}.v${sample.operation.version}`;
34
+ const arr = groups.get(key) ?? [];
35
+ arr.push(sample);
36
+ groups.set(key, arr);
37
+ }
38
+ return [...groups.values()].map((group) => {
39
+ const first = group[0];
40
+ if (!first) throw new Error("Empty group in aggregation");
41
+ const durations = group.map((s) => s.durationMs).sort((a, b) => a - b);
42
+ const errors = group.filter((s) => !s.success);
43
+ const totalCalls = group.length;
44
+ const topErrors = errors.reduce((acc, sample) => {
45
+ if (!sample.errorCode) return acc;
46
+ acc[sample.errorCode] = (acc[sample.errorCode] ?? 0) + 1;
47
+ return acc;
48
+ }, {});
49
+ const timestamps = group.map((s) => s.timestamp.getTime());
50
+ return {
51
+ operation: first.operation,
52
+ totalCalls,
53
+ successRate: (totalCalls - errors.length) / totalCalls,
54
+ errorRate: errors.length / totalCalls,
55
+ averageLatencyMs: durations.reduce((sum, value) => sum + value, 0) / totalCalls,
56
+ p95LatencyMs: percentile(durations, .95),
57
+ p99LatencyMs: percentile(durations, .99),
58
+ maxLatencyMs: Math.max(...durations),
59
+ windowStart: new Date(Math.min(...timestamps)),
60
+ windowEnd: new Date(Math.max(...timestamps)),
61
+ topErrors
62
+ };
63
+ });
64
+ }
65
+ buildSequences(samples) {
66
+ const byTrace = /* @__PURE__ */ new Map();
67
+ for (const sample of samples.slice(-this.sequenceSampleSize)) {
68
+ if (!sample.traceId) continue;
69
+ const arr = byTrace.get(sample.traceId) ?? [];
70
+ arr.push(sample);
71
+ byTrace.set(sample.traceId, arr);
72
+ }
73
+ const sequences = {};
74
+ for (const events of byTrace.values()) {
75
+ const ordered = events.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
76
+ const steps = ordered.map((event) => event.operation.name);
77
+ if (steps.length < 2) continue;
78
+ const key = `${steps.join(">")}@${ordered[0]?.tenantId ?? "global"}`;
79
+ const existing = sequences[key];
80
+ if (existing) existing.count += 1;
81
+ else sequences[key] = {
82
+ steps,
83
+ tenantId: ordered[0]?.tenantId,
84
+ count: 1
85
+ };
86
+ }
87
+ return Object.values(sequences).sort((a, b) => b.count - a.count);
88
+ }
89
+ };
90
+ function percentile(values, ratio) {
91
+ if (!values.length) return 0;
92
+ if (values.length === 1) return values[0] ?? 0;
93
+ return values[Math.min(values.length - 1, Math.floor(ratio * values.length))] ?? 0;
94
+ }
95
+
96
+ //#endregion
97
+ export { IntentAggregator };
98
+ //# sourceMappingURL=aggregator.mjs.map
@@ -0,0 +1,32 @@
1
+ import { AggregatedOperationMetrics, OperationSequence } from "./aggregator.mjs";
2
+
3
+ //#region src/intent/detector.d.ts
4
+ type IntentSignalType = 'latency-regression' | 'error-spike' | 'throughput-drop' | 'missing-workflow-step';
5
+ interface IntentSignal {
6
+ id: string;
7
+ type: IntentSignalType;
8
+ operation?: AggregatedOperationMetrics['operation'];
9
+ confidence: number;
10
+ description: string;
11
+ metadata?: Record<string, unknown>;
12
+ evidence: {
13
+ type: 'metric' | 'sequence' | 'anomaly';
14
+ description: string;
15
+ data?: Record<string, unknown>;
16
+ }[];
17
+ }
18
+ interface IntentDetectorOptions {
19
+ errorRateThreshold?: number;
20
+ latencyP99ThresholdMs?: number;
21
+ throughputDropThreshold?: number;
22
+ minSequenceLength?: number;
23
+ }
24
+ declare class IntentDetector {
25
+ private readonly options;
26
+ constructor(options?: IntentDetectorOptions);
27
+ detectFromMetrics(current: AggregatedOperationMetrics[], previous?: AggregatedOperationMetrics[]): IntentSignal[];
28
+ detectSequentialIntents(sequences: OperationSequence[]): IntentSignal[];
29
+ }
30
+ //#endregion
31
+ export { IntentDetector, IntentDetectorOptions, IntentSignal, IntentSignalType };
32
+ //# sourceMappingURL=detector.d.mts.map
@@ -1 +1,122 @@
1
- import{randomUUID as e}from"node:crypto";const t={errorRateThreshold:.05,latencyP99ThresholdMs:750,throughputDropThreshold:.3,minSequenceLength:3};var n=class{options;constructor(e={}){this.options={errorRateThreshold:e.errorRateThreshold??t.errorRateThreshold,latencyP99ThresholdMs:e.latencyP99ThresholdMs??t.latencyP99ThresholdMs,throughputDropThreshold:e.throughputDropThreshold??t.throughputDropThreshold,minSequenceLength:e.minSequenceLength??t.minSequenceLength}}detectFromMetrics(t,n){let r=[],i=new Map((n??[]).map(e=>[`${e.operation.name}.v${e.operation.version}`,e]));for(let n of t){if(n.errorRate>=this.options.errorRateThreshold){r.push({id:e(),type:`error-spike`,operation:n.operation,confidence:Math.min(1,n.errorRate/this.options.errorRateThreshold),description:`Error rate ${n.errorRate.toFixed(2)} exceeded threshold`,metadata:{errorRate:n.errorRate,topErrors:n.topErrors},evidence:[{type:`metric`,description:`error-rate`,data:{errorRate:n.errorRate,threshold:this.options.errorRateThreshold}}]});continue}if(n.p99LatencyMs>=this.options.latencyP99ThresholdMs){r.push({id:e(),type:`latency-regression`,operation:n.operation,confidence:Math.min(1,n.p99LatencyMs/this.options.latencyP99ThresholdMs),description:`P99 latency ${n.p99LatencyMs}ms exceeded threshold`,metadata:{p99LatencyMs:n.p99LatencyMs},evidence:[{type:`metric`,description:`p99-latency`,data:{p99LatencyMs:n.p99LatencyMs,threshold:this.options.latencyP99ThresholdMs}}]});continue}let t=i.get(`${n.operation.name}.v${n.operation.version}`);if(t){let i=(t.totalCalls-n.totalCalls)/Math.max(t.totalCalls,1);i>=this.options.throughputDropThreshold&&r.push({id:e(),type:`throughput-drop`,operation:n.operation,confidence:Math.min(1,i/this.options.throughputDropThreshold),description:`Throughput dropped ${(i*100).toFixed(1)}% vs baseline`,metadata:{baselineCalls:t.totalCalls,currentCalls:n.totalCalls},evidence:[{type:`metric`,description:`throughput-drop`,data:{baselineCalls:t.totalCalls,currentCalls:n.totalCalls}}]})}}return r}detectSequentialIntents(t){let n=[];for(let r of t){if(r.steps.length<this.options.minSequenceLength)continue;let t=r.steps.join(` → `);n.push({id:e(),type:`missing-workflow-step`,confidence:.6,description:`Repeated workflow detected: ${t}`,metadata:{steps:r.steps,tenantId:r.tenantId,occurrences:r.count},evidence:[{type:`sequence`,description:`sequential-calls`,data:{steps:r.steps,count:r.count}}]})}return n}};export{n as IntentDetector};
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ //#region src/intent/detector.ts
4
+ const DEFAULTS = {
5
+ errorRateThreshold: .05,
6
+ latencyP99ThresholdMs: 750,
7
+ throughputDropThreshold: .3,
8
+ minSequenceLength: 3
9
+ };
10
+ var IntentDetector = class {
11
+ options;
12
+ constructor(options = {}) {
13
+ this.options = {
14
+ errorRateThreshold: options.errorRateThreshold ?? DEFAULTS.errorRateThreshold,
15
+ latencyP99ThresholdMs: options.latencyP99ThresholdMs ?? DEFAULTS.latencyP99ThresholdMs,
16
+ throughputDropThreshold: options.throughputDropThreshold ?? DEFAULTS.throughputDropThreshold,
17
+ minSequenceLength: options.minSequenceLength ?? DEFAULTS.minSequenceLength
18
+ };
19
+ }
20
+ detectFromMetrics(current, previous) {
21
+ const signals = [];
22
+ const baseline = new Map((previous ?? []).map((metric) => [`${metric.operation.name}.v${metric.operation.version}`, metric]));
23
+ for (const metric of current) {
24
+ if (metric.errorRate >= this.options.errorRateThreshold) {
25
+ signals.push({
26
+ id: randomUUID(),
27
+ type: "error-spike",
28
+ operation: metric.operation,
29
+ confidence: Math.min(1, metric.errorRate / this.options.errorRateThreshold),
30
+ description: `Error rate ${metric.errorRate.toFixed(2)} exceeded threshold`,
31
+ metadata: {
32
+ errorRate: metric.errorRate,
33
+ topErrors: metric.topErrors
34
+ },
35
+ evidence: [{
36
+ type: "metric",
37
+ description: "error-rate",
38
+ data: {
39
+ errorRate: metric.errorRate,
40
+ threshold: this.options.errorRateThreshold
41
+ }
42
+ }]
43
+ });
44
+ continue;
45
+ }
46
+ if (metric.p99LatencyMs >= this.options.latencyP99ThresholdMs) {
47
+ signals.push({
48
+ id: randomUUID(),
49
+ type: "latency-regression",
50
+ operation: metric.operation,
51
+ confidence: Math.min(1, metric.p99LatencyMs / this.options.latencyP99ThresholdMs),
52
+ description: `P99 latency ${metric.p99LatencyMs}ms exceeded threshold`,
53
+ metadata: { p99LatencyMs: metric.p99LatencyMs },
54
+ evidence: [{
55
+ type: "metric",
56
+ description: "p99-latency",
57
+ data: {
58
+ p99LatencyMs: metric.p99LatencyMs,
59
+ threshold: this.options.latencyP99ThresholdMs
60
+ }
61
+ }]
62
+ });
63
+ continue;
64
+ }
65
+ const base = baseline.get(`${metric.operation.name}.v${metric.operation.version}`);
66
+ if (base) {
67
+ const drop = (base.totalCalls - metric.totalCalls) / Math.max(base.totalCalls, 1);
68
+ if (drop >= this.options.throughputDropThreshold) signals.push({
69
+ id: randomUUID(),
70
+ type: "throughput-drop",
71
+ operation: metric.operation,
72
+ confidence: Math.min(1, drop / this.options.throughputDropThreshold),
73
+ description: `Throughput dropped ${(drop * 100).toFixed(1)}% vs baseline`,
74
+ metadata: {
75
+ baselineCalls: base.totalCalls,
76
+ currentCalls: metric.totalCalls
77
+ },
78
+ evidence: [{
79
+ type: "metric",
80
+ description: "throughput-drop",
81
+ data: {
82
+ baselineCalls: base.totalCalls,
83
+ currentCalls: metric.totalCalls
84
+ }
85
+ }]
86
+ });
87
+ }
88
+ }
89
+ return signals;
90
+ }
91
+ detectSequentialIntents(sequences) {
92
+ const signals = [];
93
+ for (const sequence of sequences) {
94
+ if (sequence.steps.length < this.options.minSequenceLength) continue;
95
+ const description = sequence.steps.join(" → ");
96
+ signals.push({
97
+ id: randomUUID(),
98
+ type: "missing-workflow-step",
99
+ confidence: .6,
100
+ description: `Repeated workflow detected: ${description}`,
101
+ metadata: {
102
+ steps: sequence.steps,
103
+ tenantId: sequence.tenantId,
104
+ occurrences: sequence.count
105
+ },
106
+ evidence: [{
107
+ type: "sequence",
108
+ description: "sequential-calls",
109
+ data: {
110
+ steps: sequence.steps,
111
+ count: sequence.count
112
+ }
113
+ }]
114
+ });
115
+ }
116
+ return signals;
117
+ }
118
+ };
119
+
120
+ //#endregion
121
+ export { IntentDetector };
122
+ //# sourceMappingURL=detector.mjs.map
@@ -0,0 +1,20 @@
1
+ //#region src/logging/index.d.ts
2
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
3
+ interface LogEntry {
4
+ level: LogLevel;
5
+ message: string;
6
+ [key: string]: unknown;
7
+ }
8
+ declare class Logger {
9
+ private readonly serviceName;
10
+ constructor(serviceName: string);
11
+ private log;
12
+ debug(message: string, meta?: Record<string, unknown>): void;
13
+ info(message: string, meta?: Record<string, unknown>): void;
14
+ warn(message: string, meta?: Record<string, unknown>): void;
15
+ error(message: string, meta?: Record<string, unknown>): void;
16
+ }
17
+ declare const logger: Logger;
18
+ //#endregion
19
+ export { LogEntry, LogLevel, Logger, logger };
20
+ //# sourceMappingURL=index.d.mts.map
@@ -1 +1,40 @@
1
- import{context as e,trace as t}from"@opentelemetry/api";var n=class{constructor(e){this.serviceName=e}log(n,r,i={}){let a=t.getSpan(e.active()),o=a?.spanContext().traceId,s=a?.spanContext().spanId,c={timestamp:new Date().toISOString(),service:this.serviceName,level:n,message:r,traceId:o,spanId:s,...i};console.log(JSON.stringify(c))}debug(e,t){this.log(`debug`,e,t)}info(e,t){this.log(`info`,e,t)}warn(e,t){this.log(`warn`,e,t)}error(e,t){this.log(`error`,e,t)}};const r=new n(process.env.OTEL_SERVICE_NAME||`unknown-service`);export{n as Logger,r as logger};
1
+ import { context, trace } from "@opentelemetry/api";
2
+
3
+ //#region src/logging/index.ts
4
+ var Logger = class {
5
+ constructor(serviceName) {
6
+ this.serviceName = serviceName;
7
+ }
8
+ log(level, message, meta = {}) {
9
+ const span = trace.getSpan(context.active());
10
+ const traceId = span?.spanContext().traceId;
11
+ const spanId = span?.spanContext().spanId;
12
+ const entry = {
13
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
14
+ service: this.serviceName,
15
+ level,
16
+ message,
17
+ traceId,
18
+ spanId,
19
+ ...meta
20
+ };
21
+ console.log(JSON.stringify(entry));
22
+ }
23
+ debug(message, meta) {
24
+ this.log("debug", message, meta);
25
+ }
26
+ info(message, meta) {
27
+ this.log("info", message, meta);
28
+ }
29
+ warn(message, meta) {
30
+ this.log("warn", message, meta);
31
+ }
32
+ error(message, meta) {
33
+ this.log("error", message, meta);
34
+ }
35
+ };
36
+ const logger = new Logger(process.env.OTEL_SERVICE_NAME || "unknown-service");
37
+
38
+ //#endregion
39
+ export { Logger, logger };
40
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,17 @@
1
+ import * as _opentelemetry_api0 from "@opentelemetry/api";
2
+ import { Counter, Histogram, Meter, UpDownCounter } from "@opentelemetry/api";
3
+
4
+ //#region src/metrics/index.d.ts
5
+ declare function getMeter(name?: string): Meter;
6
+ declare function createCounter(name: string, description?: string, meterName?: string): Counter;
7
+ declare function createUpDownCounter(name: string, description?: string, meterName?: string): UpDownCounter;
8
+ declare function createHistogram(name: string, description?: string, meterName?: string): Histogram;
9
+ declare const standardMetrics: {
10
+ httpRequests: Counter<_opentelemetry_api0.Attributes>;
11
+ httpDuration: Histogram<_opentelemetry_api0.Attributes>;
12
+ operationErrors: Counter<_opentelemetry_api0.Attributes>;
13
+ workflowDuration: Histogram<_opentelemetry_api0.Attributes>;
14
+ };
15
+ //#endregion
16
+ export { createCounter, createHistogram, createUpDownCounter, getMeter, standardMetrics };
17
+ //# sourceMappingURL=index.d.mts.map
@@ -1 +1,26 @@
1
- import{metrics as e}from"@opentelemetry/api";function t(t=`@lssm/lib.observability`){return e.getMeter(t)}function n(e,n,r){return t(r).createCounter(e,{description:n})}function r(e,n,r){return t(r).createUpDownCounter(e,{description:n})}function i(e,n,r){return t(r).createHistogram(e,{description:n})}const a={httpRequests:n(`http_requests_total`,`Total HTTP requests`),httpDuration:i(`http_request_duration_seconds`,`HTTP request duration`),operationErrors:n(`operation_errors_total`,`Total operation errors`),workflowDuration:i(`workflow_duration_seconds`,`Workflow execution duration`)};export{n as createCounter,i as createHistogram,r as createUpDownCounter,t as getMeter,a as standardMetrics};
1
+ import { metrics } from "@opentelemetry/api";
2
+
3
+ //#region src/metrics/index.ts
4
+ const DEFAULT_METER_NAME = "@lssm/lib.observability";
5
+ function getMeter(name = DEFAULT_METER_NAME) {
6
+ return metrics.getMeter(name);
7
+ }
8
+ function createCounter(name, description, meterName) {
9
+ return getMeter(meterName).createCounter(name, { description });
10
+ }
11
+ function createUpDownCounter(name, description, meterName) {
12
+ return getMeter(meterName).createUpDownCounter(name, { description });
13
+ }
14
+ function createHistogram(name, description, meterName) {
15
+ return getMeter(meterName).createHistogram(name, { description });
16
+ }
17
+ const standardMetrics = {
18
+ httpRequests: createCounter("http_requests_total", "Total HTTP requests"),
19
+ httpDuration: createHistogram("http_request_duration_seconds", "HTTP request duration"),
20
+ operationErrors: createCounter("operation_errors_total", "Total operation errors"),
21
+ workflowDuration: createHistogram("workflow_duration_seconds", "Workflow execution duration")
22
+ };
23
+
24
+ //#endregion
25
+ export { createCounter, createHistogram, createUpDownCounter, getMeter, standardMetrics };
26
+ //# sourceMappingURL=index.mjs.map
@@ -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
@@ -1 +1,66 @@
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};
1
+ import { IntentAggregator } from "../intent/aggregator.mjs";
2
+ import { IntentDetector } from "../intent/detector.mjs";
3
+ import { EventEmitter } from "node:events";
4
+
5
+ //#region src/pipeline/evolution-pipeline.ts
6
+ var EvolutionPipeline = class {
7
+ detector;
8
+ aggregator;
9
+ emitter;
10
+ onIntent;
11
+ onSnapshot;
12
+ timer;
13
+ previousMetrics;
14
+ constructor(options = {}) {
15
+ this.detector = options.detector ?? new IntentDetector();
16
+ this.aggregator = options.aggregator ?? new IntentAggregator();
17
+ this.emitter = options.emitter ?? new EventEmitter();
18
+ this.onIntent = options.onIntent;
19
+ this.onSnapshot = options.onSnapshot;
20
+ }
21
+ ingest(sample) {
22
+ this.aggregator.add(sample);
23
+ }
24
+ on(listener) {
25
+ this.emitter.on("event", listener);
26
+ }
27
+ start(intervalMs = 300 * 1e3) {
28
+ this.stop();
29
+ this.timer = setInterval(() => {
30
+ this.run();
31
+ }, intervalMs);
32
+ }
33
+ stop() {
34
+ if (this.timer) {
35
+ clearInterval(this.timer);
36
+ this.timer = void 0;
37
+ }
38
+ }
39
+ async run() {
40
+ const snapshot = this.aggregator.flush();
41
+ this.emit({
42
+ type: "telemetry.window",
43
+ payload: { sampleCount: snapshot.sampleCount }
44
+ });
45
+ if (this.onSnapshot) await this.onSnapshot(snapshot);
46
+ if (!snapshot.sampleCount) return;
47
+ const metricSignals = this.detector.detectFromMetrics(snapshot.metrics, this.previousMetrics);
48
+ const sequenceSignals = this.detector.detectSequentialIntents(snapshot.sequences);
49
+ this.previousMetrics = snapshot.metrics;
50
+ const signals = [...metricSignals, ...sequenceSignals];
51
+ for (const signal of signals) {
52
+ if (this.onIntent) await this.onIntent(signal);
53
+ this.emit({
54
+ type: "intent.detected",
55
+ payload: signal
56
+ });
57
+ }
58
+ }
59
+ emit(event) {
60
+ this.emitter.emit("event", event);
61
+ }
62
+ };
63
+
64
+ //#endregion
65
+ export { EvolutionPipeline };
66
+ //# sourceMappingURL=evolution-pipeline.mjs.map
@@ -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
@@ -1 +1,73 @@
1
- import{createCounter as e,createHistogram as t,createUpDownCounter as n}from"../metrics/index.mjs";import{EventEmitter as r}from"node:events";import{getStageLabel as i}from"@lssm/lib.lifecycle";var a=class{assessmentCounter;confidenceHistogram;stageUpDownCounter;emitter;lowConfidenceThreshold;currentStageByTenant=new Map;constructor(i={}){let a=i.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=i.emitter??new r,this.lowConfidenceThreshold=i.lowConfidenceThreshold??.4}recordAssessment(e,t){let n={stage:i(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:i(n),tenantId:t}),this.stageUpDownCounter.add(1,{stage:i(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};
1
+ import { createCounter, createHistogram, createUpDownCounter } from "../metrics/index.mjs";
2
+ import { EventEmitter } from "node:events";
3
+ import { getStageLabel } from "@lssm/lib.lifecycle";
4
+
5
+ //#region src/pipeline/lifecycle-pipeline.ts
6
+ var LifecycleKpiPipeline = class {
7
+ assessmentCounter;
8
+ confidenceHistogram;
9
+ stageUpDownCounter;
10
+ emitter;
11
+ lowConfidenceThreshold;
12
+ currentStageByTenant = /* @__PURE__ */ new Map();
13
+ constructor(options = {}) {
14
+ const meterName = options.meterName ?? "@lssm/lib.lifecycle-kpi";
15
+ this.assessmentCounter = createCounter("lifecycle_assessments_total", "Total lifecycle assessments", meterName);
16
+ this.confidenceHistogram = createHistogram("lifecycle_assessment_confidence", "Lifecycle assessment confidence distribution", meterName);
17
+ this.stageUpDownCounter = createUpDownCounter("lifecycle_stage_tenants", "Current tenants per lifecycle stage", meterName);
18
+ this.emitter = options.emitter ?? new EventEmitter();
19
+ this.lowConfidenceThreshold = options.lowConfidenceThreshold ?? .4;
20
+ }
21
+ recordAssessment(assessment, tenantId) {
22
+ const attributes = {
23
+ stage: getStageLabel(assessment.stage),
24
+ tenantId
25
+ };
26
+ this.assessmentCounter.add(1, attributes);
27
+ this.confidenceHistogram.record(assessment.confidence, attributes);
28
+ this.ensureStageCounters(assessment.stage, tenantId);
29
+ this.emitter.emit("event", {
30
+ type: "assessment.recorded",
31
+ payload: {
32
+ tenantId,
33
+ stage: assessment.stage
34
+ }
35
+ });
36
+ if (assessment.confidence < this.lowConfidenceThreshold) this.emitter.emit("event", {
37
+ type: "confidence.low",
38
+ payload: {
39
+ tenantId,
40
+ confidence: assessment.confidence
41
+ }
42
+ });
43
+ }
44
+ on(listener) {
45
+ this.emitter.on("event", listener);
46
+ }
47
+ ensureStageCounters(stage, tenantId) {
48
+ if (!tenantId) return;
49
+ const previous = this.currentStageByTenant.get(tenantId);
50
+ if (previous === stage) return;
51
+ if (previous !== void 0) this.stageUpDownCounter.add(-1, {
52
+ stage: getStageLabel(previous),
53
+ tenantId
54
+ });
55
+ this.stageUpDownCounter.add(1, {
56
+ stage: getStageLabel(stage),
57
+ tenantId
58
+ });
59
+ this.currentStageByTenant.set(tenantId, stage);
60
+ this.emitter.emit("event", {
61
+ type: "stage.changed",
62
+ payload: {
63
+ tenantId,
64
+ previousStage: previous,
65
+ nextStage: stage
66
+ }
67
+ });
68
+ }
69
+ };
70
+
71
+ //#endregion
72
+ export { LifecycleKpiPipeline };
73
+ //# sourceMappingURL=lifecycle-pipeline.mjs.map
@@ -0,0 +1,9 @@
1
+ import { Span, Tracer } from "@opentelemetry/api";
2
+
3
+ //#region src/tracing/index.d.ts
4
+ declare function getTracer(name?: string): Tracer;
5
+ declare function traceAsync<T>(name: string, fn: (span: Span) => Promise<T>, tracerName?: string): Promise<T>;
6
+ declare function traceSync<T>(name: string, fn: (span: Span) => T, tracerName?: string): T;
7
+ //#endregion
8
+ export { getTracer, traceAsync, traceSync };
9
+ //# sourceMappingURL=index.d.mts.map
@@ -1 +1,47 @@
1
- import{SpanStatusCode as e,trace as t}from"@opentelemetry/api";function n(e=`@lssm/lib.observability`){return t.getTracer(e)}async function r(t,r,i){return n(i).startActiveSpan(t,async t=>{try{let n=await r(t);return t.setStatus({code:e.OK}),n}catch(n){throw t.recordException(n),t.setStatus({code:e.ERROR,message:n instanceof Error?n.message:String(n)}),n}finally{t.end()}})}function i(t,r,i){return n(i).startActiveSpan(t,t=>{try{let n=r(t);return t.setStatus({code:e.OK}),n}catch(n){throw t.recordException(n),t.setStatus({code:e.ERROR,message:n instanceof Error?n.message:String(n)}),n}finally{t.end()}})}export{n as getTracer,r as traceAsync,i as traceSync};
1
+ import { SpanStatusCode, trace } from "@opentelemetry/api";
2
+
3
+ //#region src/tracing/index.ts
4
+ const DEFAULT_TRACER_NAME = "@lssm/lib.observability";
5
+ function getTracer(name = DEFAULT_TRACER_NAME) {
6
+ return trace.getTracer(name);
7
+ }
8
+ async function traceAsync(name, fn, tracerName) {
9
+ return getTracer(tracerName).startActiveSpan(name, async (span) => {
10
+ try {
11
+ const result = await fn(span);
12
+ span.setStatus({ code: SpanStatusCode.OK });
13
+ return result;
14
+ } catch (error) {
15
+ span.recordException(error);
16
+ span.setStatus({
17
+ code: SpanStatusCode.ERROR,
18
+ message: error instanceof Error ? error.message : String(error)
19
+ });
20
+ throw error;
21
+ } finally {
22
+ span.end();
23
+ }
24
+ });
25
+ }
26
+ function traceSync(name, fn, tracerName) {
27
+ return getTracer(tracerName).startActiveSpan(name, (span) => {
28
+ try {
29
+ const result = fn(span);
30
+ span.setStatus({ code: SpanStatusCode.OK });
31
+ return result;
32
+ } catch (error) {
33
+ span.recordException(error);
34
+ span.setStatus({
35
+ code: SpanStatusCode.ERROR,
36
+ message: error instanceof Error ? error.message : String(error)
37
+ });
38
+ throw error;
39
+ } finally {
40
+ span.end();
41
+ }
42
+ });
43
+ }
44
+
45
+ //#endregion
46
+ export { getTracer, traceAsync, traceSync };
47
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,19 @@
1
+ import { TelemetrySample } from "../intent/aggregator.mjs";
2
+
3
+ //#region src/tracing/middleware.d.ts
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>;
17
+ //#endregion
18
+ export { TracingMiddlewareOptions, createTracingMiddleware };
19
+ //# sourceMappingURL=middleware.d.mts.map
@@ -1 +1,80 @@
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};
1
+ import { traceAsync } from "./index.mjs";
2
+ import { standardMetrics } from "../metrics/index.mjs";
3
+
4
+ //#region src/tracing/middleware.ts
5
+ function createTracingMiddleware(options = {}) {
6
+ return async (req, next) => {
7
+ const method = req.method;
8
+ const path = new URL(req.url).pathname;
9
+ standardMetrics.httpRequests.add(1, {
10
+ method,
11
+ path
12
+ });
13
+ const startTime = performance.now();
14
+ return traceAsync(`HTTP ${method} ${path}`, async (span) => {
15
+ span.setAttribute("http.method", method);
16
+ span.setAttribute("http.url", req.url);
17
+ try {
18
+ const response = await next();
19
+ span.setAttribute("http.status_code", response.status);
20
+ const duration = (performance.now() - startTime) / 1e3;
21
+ standardMetrics.httpDuration.record(duration, {
22
+ method,
23
+ path,
24
+ status: response.status.toString()
25
+ });
26
+ emitTelemetrySample({
27
+ req,
28
+ res: response,
29
+ span,
30
+ success: true,
31
+ durationMs: duration * 1e3,
32
+ options
33
+ });
34
+ return response;
35
+ } catch (error) {
36
+ standardMetrics.operationErrors.add(1, {
37
+ method,
38
+ path
39
+ });
40
+ emitTelemetrySample({
41
+ req,
42
+ span,
43
+ success: false,
44
+ durationMs: performance.now() - startTime,
45
+ error,
46
+ options
47
+ });
48
+ throw error;
49
+ }
50
+ });
51
+ };
52
+ }
53
+ function emitTelemetrySample({ req, res, span, success, durationMs, error, options }) {
54
+ if (!options.onSample || !options.resolveOperation) return;
55
+ const operation = options.resolveOperation({
56
+ req,
57
+ res
58
+ });
59
+ if (!operation) return;
60
+ const sample = {
61
+ operation,
62
+ durationMs,
63
+ success,
64
+ timestamp: /* @__PURE__ */ new Date(),
65
+ errorCode: !success && error instanceof Error ? error.name : success ? void 0 : "unknown",
66
+ tenantId: options.tenantResolver?.(req),
67
+ actorId: options.actorResolver?.(req),
68
+ traceId: span.spanContext().traceId,
69
+ metadata: {
70
+ method: req.method,
71
+ path: new URL(req.url).pathname,
72
+ status: res?.status
73
+ }
74
+ };
75
+ options.onSample(sample);
76
+ }
77
+
78
+ //#endregion
79
+ export { createTracingMiddleware };
80
+ //# sourceMappingURL=middleware.mjs.map
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@lssm/lib.observability",
3
- "version": "1.42.0",
3
+ "version": "1.42.2",
4
4
  "main": "./dist/index.mjs",
5
5
  "types": "./dist/index.d.mts",
6
6
  "scripts": {
7
7
  "publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
8
8
  "publish:pkg:canary": "bun publish:pkg --tag canary",
9
- "build": "bun build:bundle && bun build:types",
9
+ "build": "bun build:types && bun build:bundle",
10
10
  "build:bundle": "tsdown",
11
11
  "build:types": "tsc --noEmit",
12
12
  "dev": "bun build:bundle --watch",
@@ -17,28 +17,30 @@
17
17
  "test": "bun run"
18
18
  },
19
19
  "dependencies": {
20
- "@lssm/lib.lifecycle": "1.42.0"
20
+ "@lssm/lib.lifecycle": "1.42.2"
21
21
  },
22
22
  "peerDependencies": {
23
23
  "@opentelemetry/api": "*"
24
24
  },
25
25
  "devDependencies": {
26
- "typescript": "^5.0.0"
26
+ "@lssm/tool.tsdown": "1.42.2",
27
+ "@lssm/tool.typescript": "1.42.2",
28
+ "typescript": "^5.9.3"
27
29
  },
28
30
  "exports": {
29
- ".": "./src/index.ts",
30
- "./anomaly/alert-manager": "./src/anomaly/alert-manager.ts",
31
- "./anomaly/anomaly-detector": "./src/anomaly/anomaly-detector.ts",
32
- "./anomaly/baseline-calculator": "./src/anomaly/baseline-calculator.ts",
33
- "./anomaly/root-cause-analyzer": "./src/anomaly/root-cause-analyzer.ts",
34
- "./intent/aggregator": "./src/intent/aggregator.ts",
35
- "./intent/detector": "./src/intent/detector.ts",
36
- "./logging": "./src/logging/index.ts",
37
- "./metrics": "./src/metrics/index.ts",
38
- "./pipeline/evolution-pipeline": "./src/pipeline/evolution-pipeline.ts",
39
- "./pipeline/lifecycle-pipeline": "./src/pipeline/lifecycle-pipeline.ts",
40
- "./tracing": "./src/tracing/index.ts",
41
- "./tracing/middleware": "./src/tracing/middleware.ts",
31
+ ".": "./dist/index.mjs",
32
+ "./anomaly/alert-manager": "./dist/anomaly/alert-manager.mjs",
33
+ "./anomaly/anomaly-detector": "./dist/anomaly/anomaly-detector.mjs",
34
+ "./anomaly/baseline-calculator": "./dist/anomaly/baseline-calculator.mjs",
35
+ "./anomaly/root-cause-analyzer": "./dist/anomaly/root-cause-analyzer.mjs",
36
+ "./intent/aggregator": "./dist/intent/aggregator.mjs",
37
+ "./intent/detector": "./dist/intent/detector.mjs",
38
+ "./logging": "./dist/logging/index.mjs",
39
+ "./metrics": "./dist/metrics/index.mjs",
40
+ "./pipeline/evolution-pipeline": "./dist/pipeline/evolution-pipeline.mjs",
41
+ "./pipeline/lifecycle-pipeline": "./dist/pipeline/lifecycle-pipeline.mjs",
42
+ "./tracing": "./dist/tracing/index.mjs",
43
+ "./tracing/middleware": "./dist/tracing/middleware.mjs",
42
44
  "./*": "./*"
43
45
  },
44
46
  "module": "./dist/index.mjs",
@@ -59,6 +61,13 @@
59
61
  "./tracing": "./dist/tracing/index.mjs",
60
62
  "./tracing/middleware": "./dist/tracing/middleware.mjs",
61
63
  "./*": "./*"
62
- }
64
+ },
65
+ "registry": "https://registry.npmjs.org/"
66
+ },
67
+ "license": "MIT",
68
+ "repository": {
69
+ "type": "git",
70
+ "url": "https://github.com/lssm-tech/contractspec.git",
71
+ "directory": "packages/libs/observability"
63
72
  }
64
73
  }