@runtime-digital-twin/sdk 1.0.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.
- package/README.md +214 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +13 -0
- package/dist/db-wrapper.d.ts +258 -0
- package/dist/db-wrapper.d.ts.map +1 -0
- package/dist/db-wrapper.js +636 -0
- package/dist/event-envelope.d.ts +35 -0
- package/dist/event-envelope.d.ts.map +1 -0
- package/dist/event-envelope.js +101 -0
- package/dist/fastify-plugin.d.ts +29 -0
- package/dist/fastify-plugin.d.ts.map +1 -0
- package/dist/fastify-plugin.js +243 -0
- package/dist/http-sentinels.d.ts +39 -0
- package/dist/http-sentinels.d.ts.map +1 -0
- package/dist/http-sentinels.js +169 -0
- package/dist/http-wrapper.d.ts +25 -0
- package/dist/http-wrapper.d.ts.map +1 -0
- package/dist/http-wrapper.js +477 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/invariants.d.ts +58 -0
- package/dist/invariants.d.ts.map +1 -0
- package/dist/invariants.js +192 -0
- package/dist/multi-service-edge-builder.d.ts +80 -0
- package/dist/multi-service-edge-builder.d.ts.map +1 -0
- package/dist/multi-service-edge-builder.js +107 -0
- package/dist/outbound-matcher.d.ts +192 -0
- package/dist/outbound-matcher.d.ts.map +1 -0
- package/dist/outbound-matcher.js +457 -0
- package/dist/peer-service-resolver.d.ts +22 -0
- package/dist/peer-service-resolver.d.ts.map +1 -0
- package/dist/peer-service-resolver.js +85 -0
- package/dist/redaction.d.ts +111 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +487 -0
- package/dist/replay-logger.d.ts +438 -0
- package/dist/replay-logger.d.ts.map +1 -0
- package/dist/replay-logger.js +434 -0
- package/dist/root-cause-analyzer.d.ts +45 -0
- package/dist/root-cause-analyzer.d.ts.map +1 -0
- package/dist/root-cause-analyzer.js +606 -0
- package/dist/shape-digest-utils.d.ts +45 -0
- package/dist/shape-digest-utils.d.ts.map +1 -0
- package/dist/shape-digest-utils.js +154 -0
- package/dist/trace-bundle-writer.d.ts +52 -0
- package/dist/trace-bundle-writer.d.ts.map +1 -0
- package/dist/trace-bundle-writer.js +267 -0
- package/dist/trace-loader.d.ts +69 -0
- package/dist/trace-loader.d.ts.map +1 -0
- package/dist/trace-loader.js +146 -0
- package/dist/trace-uploader.d.ts +25 -0
- package/dist/trace-uploader.d.ts.map +1 -0
- package/dist/trace-uploader.js +132 -0
- package/package.json +63 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Invariant Checking
|
|
3
|
+
*
|
|
4
|
+
* Lightweight contract checking that emits state.invariant events
|
|
5
|
+
* without throwing by default (observe mode).
|
|
6
|
+
*/
|
|
7
|
+
export interface InvariantDetails {
|
|
8
|
+
paths?: string[];
|
|
9
|
+
expected?: string;
|
|
10
|
+
observed?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface EmitInvariantOptions {
|
|
13
|
+
name: string;
|
|
14
|
+
passed: boolean;
|
|
15
|
+
severity: "warn" | "error";
|
|
16
|
+
details?: InvariantDetails;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Emit a state.invariant event
|
|
20
|
+
*
|
|
21
|
+
* Uses the current trace context if available. If no trace context is available,
|
|
22
|
+
* the event is silently dropped (fail-open behavior).
|
|
23
|
+
*
|
|
24
|
+
* @param options - Invariant options
|
|
25
|
+
* @param explicitSpanId - Optional explicit spanId (overrides trace context)
|
|
26
|
+
* @param explicitParentSpanId - Optional explicit parentSpanId (overrides trace context)
|
|
27
|
+
*/
|
|
28
|
+
export declare function emitInvariant(options: EmitInvariantOptions, explicitSpanId?: string, explicitParentSpanId?: string | null): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Assert that a value is not null/undefined
|
|
31
|
+
*
|
|
32
|
+
* @param path - JSONPath-like path to the value (e.g., "pricing.price")
|
|
33
|
+
* @param value - Value to check
|
|
34
|
+
* @param options - Optional details
|
|
35
|
+
*/
|
|
36
|
+
export declare function assertNonNull(path: string, value: unknown, options?: {
|
|
37
|
+
expected?: string;
|
|
38
|
+
observed?: string;
|
|
39
|
+
}): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Assert that an object has all required keys
|
|
42
|
+
*
|
|
43
|
+
* @param keys - Array of required key names
|
|
44
|
+
* @param obj - Object to check
|
|
45
|
+
* @param path - Optional JSONPath-like path prefix (e.g., "response")
|
|
46
|
+
*/
|
|
47
|
+
export declare function assertHasKeys(keys: string[], obj: Record<string, unknown> | null | undefined, path?: string): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Assert that a numeric value is within a range
|
|
50
|
+
*
|
|
51
|
+
* @param name - Name of the invariant (e.g., "pricing.price_range")
|
|
52
|
+
* @param value - Value to check
|
|
53
|
+
* @param min - Minimum value (inclusive)
|
|
54
|
+
* @param max - Maximum value (inclusive)
|
|
55
|
+
* @param path - Optional JSONPath-like path to the value
|
|
56
|
+
*/
|
|
57
|
+
export declare function assertInRange(name: string, value: number | null | undefined, min: number, max: number, path?: string): Promise<void>;
|
|
58
|
+
//# sourceMappingURL=invariants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invariants.d.ts","sourceRoot":"","sources":["../src/invariants.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,cAAc,CAAC,EAAE,MAAM,EACvB,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,GACnC,OAAO,CAAC,IAAI,CAAC,CA0Df;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GACA,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,EAC/C,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAyCf"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* State Invariant Checking
|
|
4
|
+
*
|
|
5
|
+
* Lightweight contract checking that emits state.invariant events
|
|
6
|
+
* without throwing by default (observe mode).
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.emitInvariant = emitInvariant;
|
|
10
|
+
exports.assertNonNull = assertNonNull;
|
|
11
|
+
exports.assertHasKeys = assertHasKeys;
|
|
12
|
+
exports.assertInRange = assertInRange;
|
|
13
|
+
const http_wrapper_1 = require("./http-wrapper");
|
|
14
|
+
/**
|
|
15
|
+
* Emit a state.invariant event
|
|
16
|
+
*
|
|
17
|
+
* Uses the current trace context if available. If no trace context is available,
|
|
18
|
+
* the event is silently dropped (fail-open behavior).
|
|
19
|
+
*
|
|
20
|
+
* @param options - Invariant options
|
|
21
|
+
* @param explicitSpanId - Optional explicit spanId (overrides trace context)
|
|
22
|
+
* @param explicitParentSpanId - Optional explicit parentSpanId (overrides trace context)
|
|
23
|
+
*/
|
|
24
|
+
async function emitInvariant(options, explicitSpanId, explicitParentSpanId) {
|
|
25
|
+
const { name, passed, severity, details } = options;
|
|
26
|
+
const { bundle, spanId: contextSpanId } = (0, http_wrapper_1.getTraceContext)();
|
|
27
|
+
// Use explicit spanId if provided, otherwise use context
|
|
28
|
+
const spanId = explicitSpanId || contextSpanId;
|
|
29
|
+
if (process.env.DEBUG_HTTP_SENTINELS) {
|
|
30
|
+
console.log('[INVARIANTS] emitInvariant called:', {
|
|
31
|
+
name,
|
|
32
|
+
passed,
|
|
33
|
+
severity,
|
|
34
|
+
explicitSpanId,
|
|
35
|
+
contextSpanId,
|
|
36
|
+
finalSpanId: spanId,
|
|
37
|
+
hasBundle: !!bundle,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// If no trace context and no explicit spanId, silently skip (fail-open)
|
|
41
|
+
if (!bundle || !spanId) {
|
|
42
|
+
if (process.env.DEBUG_HTTP_SENTINELS) {
|
|
43
|
+
console.warn('[INVARIANTS] No trace context or spanId, skipping invariant');
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Use explicit parentSpanId if provided, otherwise null
|
|
48
|
+
const parentSpanId = explicitParentSpanId !== undefined ? explicitParentSpanId : null;
|
|
49
|
+
try {
|
|
50
|
+
const event = {
|
|
51
|
+
type: "state.invariant",
|
|
52
|
+
timestamp: Date.now(),
|
|
53
|
+
spanId,
|
|
54
|
+
parentSpanId,
|
|
55
|
+
name,
|
|
56
|
+
passed,
|
|
57
|
+
severity,
|
|
58
|
+
...(details && { details }),
|
|
59
|
+
};
|
|
60
|
+
if (process.env.DEBUG_HTTP_SENTINELS) {
|
|
61
|
+
console.log('[INVARIANTS] Writing invariant event:', event);
|
|
62
|
+
}
|
|
63
|
+
await bundle.writeEvent(event);
|
|
64
|
+
if (process.env.DEBUG_HTTP_SENTINELS) {
|
|
65
|
+
console.log('[INVARIANTS] Invariant event written successfully');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// Fail-open: log error but don't throw
|
|
70
|
+
console.error(`[SDK] Failed to emit invariant event: ${error}`);
|
|
71
|
+
if (process.env.DEBUG_HTTP_SENTINELS) {
|
|
72
|
+
console.error('[INVARIANTS] Error details:', error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Assert that a value is not null/undefined
|
|
78
|
+
*
|
|
79
|
+
* @param path - JSONPath-like path to the value (e.g., "pricing.price")
|
|
80
|
+
* @param value - Value to check
|
|
81
|
+
* @param options - Optional details
|
|
82
|
+
*/
|
|
83
|
+
async function assertNonNull(path, value, options) {
|
|
84
|
+
const passed = value !== null && value !== undefined;
|
|
85
|
+
if (!passed) {
|
|
86
|
+
await emitInvariant({
|
|
87
|
+
name: `${path}.non_null`,
|
|
88
|
+
passed: false,
|
|
89
|
+
severity: "error",
|
|
90
|
+
details: {
|
|
91
|
+
paths: [path],
|
|
92
|
+
expected: options?.expected || "non-null value",
|
|
93
|
+
observed: options?.observed || "null or undefined",
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Also emit passed invariants for completeness (optional, can be filtered)
|
|
99
|
+
await emitInvariant({
|
|
100
|
+
name: `${path}.non_null`,
|
|
101
|
+
passed: true,
|
|
102
|
+
severity: "warn", // Lower severity for passed checks
|
|
103
|
+
details: {
|
|
104
|
+
paths: [path],
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Assert that an object has all required keys
|
|
111
|
+
*
|
|
112
|
+
* @param keys - Array of required key names
|
|
113
|
+
* @param obj - Object to check
|
|
114
|
+
* @param path - Optional JSONPath-like path prefix (e.g., "response")
|
|
115
|
+
*/
|
|
116
|
+
async function assertHasKeys(keys, obj, path) {
|
|
117
|
+
if (!obj || typeof obj !== "object") {
|
|
118
|
+
await emitInvariant({
|
|
119
|
+
name: path ? `${path}.has_keys` : "object.has_keys",
|
|
120
|
+
passed: false,
|
|
121
|
+
severity: "error",
|
|
122
|
+
details: {
|
|
123
|
+
paths: path ? [path] : undefined,
|
|
124
|
+
expected: `object with keys: ${keys.join(", ")}`,
|
|
125
|
+
observed: obj === null ? "null" : obj === undefined ? "undefined" : typeof obj,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const missingKeys = keys.filter((key) => !(key in obj));
|
|
131
|
+
const passed = missingKeys.length === 0;
|
|
132
|
+
await emitInvariant({
|
|
133
|
+
name: path ? `${path}.has_keys` : "object.has_keys",
|
|
134
|
+
passed,
|
|
135
|
+
severity: passed ? "warn" : "error",
|
|
136
|
+
details: {
|
|
137
|
+
paths: path ? missingKeys.map((key) => `${path}.${key}`) : missingKeys,
|
|
138
|
+
expected: `object with keys: ${keys.join(", ")}`,
|
|
139
|
+
observed: passed
|
|
140
|
+
? `object has all required keys`
|
|
141
|
+
: `missing keys: ${missingKeys.join(", ")}`,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Assert that a numeric value is within a range
|
|
147
|
+
*
|
|
148
|
+
* @param name - Name of the invariant (e.g., "pricing.price_range")
|
|
149
|
+
* @param value - Value to check
|
|
150
|
+
* @param min - Minimum value (inclusive)
|
|
151
|
+
* @param max - Maximum value (inclusive)
|
|
152
|
+
* @param path - Optional JSONPath-like path to the value
|
|
153
|
+
*/
|
|
154
|
+
async function assertInRange(name, value, min, max, path) {
|
|
155
|
+
if (value === null || value === undefined) {
|
|
156
|
+
await emitInvariant({
|
|
157
|
+
name,
|
|
158
|
+
passed: false,
|
|
159
|
+
severity: "error",
|
|
160
|
+
details: {
|
|
161
|
+
paths: path ? [path] : undefined,
|
|
162
|
+
expected: `number in range [${min}, ${max}]`,
|
|
163
|
+
observed: value === null ? "null" : "undefined",
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (typeof value !== "number" || isNaN(value)) {
|
|
169
|
+
await emitInvariant({
|
|
170
|
+
name,
|
|
171
|
+
passed: false,
|
|
172
|
+
severity: "error",
|
|
173
|
+
details: {
|
|
174
|
+
paths: path ? [path] : undefined,
|
|
175
|
+
expected: `number in range [${min}, ${max}]`,
|
|
176
|
+
observed: `not a number: ${typeof value}`,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const passed = value >= min && value <= max;
|
|
182
|
+
await emitInvariant({
|
|
183
|
+
name,
|
|
184
|
+
passed,
|
|
185
|
+
severity: passed ? "warn" : "error",
|
|
186
|
+
details: {
|
|
187
|
+
paths: path ? [path] : undefined,
|
|
188
|
+
expected: `value in range [${min}, ${max}]`,
|
|
189
|
+
observed: passed ? `value ${value} is in range` : `value ${value} is out of range`,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-service edge builder
|
|
3
|
+
*
|
|
4
|
+
* Analyzes trace events to build edges between services for distributed tracing.
|
|
5
|
+
* An edge represents a call from one service to another.
|
|
6
|
+
*/
|
|
7
|
+
export interface TraceEvent {
|
|
8
|
+
type: string;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
traceId: string;
|
|
11
|
+
spanId: string;
|
|
12
|
+
parentSpanId: string | null;
|
|
13
|
+
serviceName: string;
|
|
14
|
+
method?: string;
|
|
15
|
+
url?: string;
|
|
16
|
+
urlTemplate?: string | null;
|
|
17
|
+
peerHost?: string | null;
|
|
18
|
+
peerService?: string | null;
|
|
19
|
+
statusCode?: number | null;
|
|
20
|
+
path?: string;
|
|
21
|
+
route?: string;
|
|
22
|
+
error?: {
|
|
23
|
+
name?: string;
|
|
24
|
+
message?: string;
|
|
25
|
+
stack?: string;
|
|
26
|
+
};
|
|
27
|
+
responseShapeDigest?: {
|
|
28
|
+
kind: string;
|
|
29
|
+
hash?: string;
|
|
30
|
+
nullPaths?: string[];
|
|
31
|
+
keys?: string[];
|
|
32
|
+
topLevelType?: string;
|
|
33
|
+
};
|
|
34
|
+
name?: string;
|
|
35
|
+
passed?: boolean;
|
|
36
|
+
severity?: "warn" | "error";
|
|
37
|
+
details?: {
|
|
38
|
+
paths?: string[];
|
|
39
|
+
expected?: string;
|
|
40
|
+
observed?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface ServiceEdge {
|
|
44
|
+
traceId: string;
|
|
45
|
+
fromService: string;
|
|
46
|
+
fromSpanId: string;
|
|
47
|
+
fromOperation: string;
|
|
48
|
+
toService: string;
|
|
49
|
+
toSpanId: string;
|
|
50
|
+
toOperation: string;
|
|
51
|
+
statusCode: number | null;
|
|
52
|
+
timestampOut: number;
|
|
53
|
+
timestampIn: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build service edges from trace events
|
|
57
|
+
*
|
|
58
|
+
* An edge is formed when:
|
|
59
|
+
* - outbound.type == "http.request.outbound"
|
|
60
|
+
* - inbound.type == "http.request.inbound"
|
|
61
|
+
* - inbound.traceId == outbound.traceId
|
|
62
|
+
* - inbound.parentSpanId == outbound.spanId
|
|
63
|
+
*
|
|
64
|
+
* @param events - Array of trace events (all from the same traceId)
|
|
65
|
+
* @returns Array of service edges, sorted by timestampOut
|
|
66
|
+
*/
|
|
67
|
+
export declare function buildEdges(events: TraceEvent[]): ServiceEdge[];
|
|
68
|
+
/**
|
|
69
|
+
* Build edges from a traceId by loading events
|
|
70
|
+
*
|
|
71
|
+
* This is a convenience function that loads events from a trace directory
|
|
72
|
+
* and calls buildEdges. For now, this requires the caller to provide events.
|
|
73
|
+
* In the future, this could load from file system or storage.
|
|
74
|
+
*
|
|
75
|
+
* @param traceId - The trace ID
|
|
76
|
+
* @param events - Array of trace events for this traceId
|
|
77
|
+
* @returns Array of service edges
|
|
78
|
+
*/
|
|
79
|
+
export declare function buildEdgesForTrace(traceId: string, events: TraceEvent[]): ServiceEdge[];
|
|
80
|
+
//# sourceMappingURL=multi-service-edge-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-service-edge-builder.d.ts","sourceRoot":"","sources":["../src/multi-service-edge-builder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IAEpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IAEF,mBAAmB,CAAC,EAAE;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IAEF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AA6BD;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,WAAW,EAAE,CAuD9D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,WAAW,EAAE,CAIvF"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Multi-service edge builder
|
|
4
|
+
*
|
|
5
|
+
* Analyzes trace events to build edges between services for distributed tracing.
|
|
6
|
+
* An edge represents a call from one service to another.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.buildEdges = buildEdges;
|
|
10
|
+
exports.buildEdgesForTrace = buildEdgesForTrace;
|
|
11
|
+
/**
|
|
12
|
+
* Build operation string for inbound event
|
|
13
|
+
*/
|
|
14
|
+
function buildInboundOperation(event) {
|
|
15
|
+
const method = event.method || 'UNKNOWN';
|
|
16
|
+
const path = event.route || event.path || 'unknown';
|
|
17
|
+
return `${method} ${path}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build operation string for outbound event
|
|
21
|
+
*/
|
|
22
|
+
function buildOutboundOperation(event) {
|
|
23
|
+
const method = event.method || 'UNKNOWN';
|
|
24
|
+
// Prefer urlTemplate, fallback to peerHost, then "unknown"
|
|
25
|
+
if (event.urlTemplate) {
|
|
26
|
+
return `${method} ${event.urlTemplate}`;
|
|
27
|
+
}
|
|
28
|
+
if (event.peerHost) {
|
|
29
|
+
return `${method} ${event.peerHost}`;
|
|
30
|
+
}
|
|
31
|
+
return `${method} unknown`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build service edges from trace events
|
|
35
|
+
*
|
|
36
|
+
* An edge is formed when:
|
|
37
|
+
* - outbound.type == "http.request.outbound"
|
|
38
|
+
* - inbound.type == "http.request.inbound"
|
|
39
|
+
* - inbound.traceId == outbound.traceId
|
|
40
|
+
* - inbound.parentSpanId == outbound.spanId
|
|
41
|
+
*
|
|
42
|
+
* @param events - Array of trace events (all from the same traceId)
|
|
43
|
+
* @returns Array of service edges, sorted by timestampOut
|
|
44
|
+
*/
|
|
45
|
+
function buildEdges(events) {
|
|
46
|
+
const edges = [];
|
|
47
|
+
// Index outbound events by spanId for fast lookup
|
|
48
|
+
const outboundBySpanId = new Map();
|
|
49
|
+
for (const event of events) {
|
|
50
|
+
if (event.type === 'http.request.outbound') {
|
|
51
|
+
outboundBySpanId.set(event.spanId, event);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Find inbound events and match them with outbound events
|
|
55
|
+
for (const inboundEvent of events) {
|
|
56
|
+
if (inboundEvent.type !== 'http.request.inbound') {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// Check if this inbound event has a parentSpanId that matches an outbound event
|
|
60
|
+
if (!inboundEvent.parentSpanId) {
|
|
61
|
+
// Root span - no parent outbound event
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const outboundEvent = outboundBySpanId.get(inboundEvent.parentSpanId);
|
|
65
|
+
if (!outboundEvent) {
|
|
66
|
+
// No matching outbound event found - downstream service not instrumented
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// Verify traceIds match (should always be true if events are from same trace)
|
|
70
|
+
if (inboundEvent.traceId !== outboundEvent.traceId) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// Build edge
|
|
74
|
+
const edge = {
|
|
75
|
+
traceId: inboundEvent.traceId,
|
|
76
|
+
fromService: outboundEvent.serviceName,
|
|
77
|
+
fromSpanId: outboundEvent.spanId,
|
|
78
|
+
fromOperation: buildOutboundOperation(outboundEvent),
|
|
79
|
+
toService: inboundEvent.serviceName,
|
|
80
|
+
toSpanId: inboundEvent.spanId,
|
|
81
|
+
toOperation: buildInboundOperation(inboundEvent),
|
|
82
|
+
statusCode: outboundEvent.statusCode ?? null,
|
|
83
|
+
timestampOut: outboundEvent.timestamp,
|
|
84
|
+
timestampIn: inboundEvent.timestamp,
|
|
85
|
+
};
|
|
86
|
+
edges.push(edge);
|
|
87
|
+
}
|
|
88
|
+
// Sort edges by timestampOut (chronological order)
|
|
89
|
+
edges.sort((a, b) => a.timestampOut - b.timestampOut);
|
|
90
|
+
return edges;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Build edges from a traceId by loading events
|
|
94
|
+
*
|
|
95
|
+
* This is a convenience function that loads events from a trace directory
|
|
96
|
+
* and calls buildEdges. For now, this requires the caller to provide events.
|
|
97
|
+
* In the future, this could load from file system or storage.
|
|
98
|
+
*
|
|
99
|
+
* @param traceId - The trace ID
|
|
100
|
+
* @param events - Array of trace events for this traceId
|
|
101
|
+
* @returns Array of service edges
|
|
102
|
+
*/
|
|
103
|
+
function buildEdgesForTrace(traceId, events) {
|
|
104
|
+
// Filter events to only include those for this traceId
|
|
105
|
+
const traceEvents = events.filter(e => e.traceId === traceId);
|
|
106
|
+
return buildEdges(traceEvents);
|
|
107
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { ReplayLogger } from "./replay-logger";
|
|
2
|
+
/**
|
|
3
|
+
* Replay modes for outbound call matching
|
|
4
|
+
*/
|
|
5
|
+
export type ReplayMode = "strict" | "explain";
|
|
6
|
+
/**
|
|
7
|
+
* Structured failure for outbound request mismatch
|
|
8
|
+
*/
|
|
9
|
+
export interface OutboundRequestMismatch {
|
|
10
|
+
type: "outbound_request_mismatch";
|
|
11
|
+
location: string;
|
|
12
|
+
expected: string;
|
|
13
|
+
actual: string;
|
|
14
|
+
hint: string;
|
|
15
|
+
callIndex: number;
|
|
16
|
+
diff: {
|
|
17
|
+
field: string;
|
|
18
|
+
expected: string | null;
|
|
19
|
+
actual: string | null;
|
|
20
|
+
}[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Structured failure for unexpected outbound call (call occurred but no recorded match)
|
|
24
|
+
*/
|
|
25
|
+
export interface UnexpectedOutboundCall {
|
|
26
|
+
type: "unexpected_outbound_call";
|
|
27
|
+
location: string;
|
|
28
|
+
expected: string;
|
|
29
|
+
actual: string;
|
|
30
|
+
hint: string;
|
|
31
|
+
callIndex: number;
|
|
32
|
+
service: string;
|
|
33
|
+
spanId: string | null;
|
|
34
|
+
call: OutboundCallSignature;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Structured failure for missing outbound call (recorded call never happened)
|
|
38
|
+
*/
|
|
39
|
+
export interface MissingOutboundCall {
|
|
40
|
+
type: "missing_outbound_call";
|
|
41
|
+
location: string;
|
|
42
|
+
expected: string;
|
|
43
|
+
actual: string;
|
|
44
|
+
hint: string;
|
|
45
|
+
callIndex: number;
|
|
46
|
+
service: string;
|
|
47
|
+
spanId: string;
|
|
48
|
+
recordedCall: RecordedOutboundCall;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Union type for all outbound call failures
|
|
52
|
+
*/
|
|
53
|
+
export type OutboundCallFailure = OutboundRequestMismatch | UnexpectedOutboundCall | MissingOutboundCall;
|
|
54
|
+
/**
|
|
55
|
+
* Divergence event emitted in explain mode
|
|
56
|
+
*/
|
|
57
|
+
export interface OutboundDivergence {
|
|
58
|
+
type: "divergence";
|
|
59
|
+
severity: "warning" | "error";
|
|
60
|
+
callIndex: number;
|
|
61
|
+
message: string;
|
|
62
|
+
expected: RecordedOutboundCall | null;
|
|
63
|
+
actual: OutboundCallSignature;
|
|
64
|
+
recoverable: boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Recorded outbound HTTP call from trace
|
|
68
|
+
*/
|
|
69
|
+
export interface RecordedOutboundCall {
|
|
70
|
+
spanId: string;
|
|
71
|
+
method: string;
|
|
72
|
+
url: string;
|
|
73
|
+
normalizedUrl: string;
|
|
74
|
+
headers: Record<string, string>;
|
|
75
|
+
bodyHash: string | null;
|
|
76
|
+
response: {
|
|
77
|
+
statusCode: number;
|
|
78
|
+
headers: Record<string, string>;
|
|
79
|
+
bodyHash: string | null;
|
|
80
|
+
bodyBlob: string | null;
|
|
81
|
+
} | null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Signature used to match an outbound call
|
|
85
|
+
*/
|
|
86
|
+
export interface OutboundCallSignature {
|
|
87
|
+
method: string;
|
|
88
|
+
url: string;
|
|
89
|
+
headers: Record<string, string>;
|
|
90
|
+
bodyHash: string | null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Normalize URL for matching: pathname + sorted query params
|
|
94
|
+
*/
|
|
95
|
+
export declare function normalizeUrl(url: string): string;
|
|
96
|
+
/**
|
|
97
|
+
* Normalize headers for matching: lowercase keys, filter to matching headers
|
|
98
|
+
*/
|
|
99
|
+
export declare function normalizeHeaders(headers: Record<string, string>): Record<string, string>;
|
|
100
|
+
/**
|
|
101
|
+
* Compute body hash from content
|
|
102
|
+
*/
|
|
103
|
+
export declare function computeBodyHash(body: string | Buffer | object | null | undefined): string | null;
|
|
104
|
+
/**
|
|
105
|
+
* Compute match signature for comparison
|
|
106
|
+
*/
|
|
107
|
+
export declare function computeMatchSignature(call: OutboundCallSignature): string;
|
|
108
|
+
/**
|
|
109
|
+
* Compare two calls and return differences
|
|
110
|
+
*/
|
|
111
|
+
export declare function compareOutboundCalls(expected: RecordedOutboundCall, actual: OutboundCallSignature): {
|
|
112
|
+
matches: boolean;
|
|
113
|
+
diffs: {
|
|
114
|
+
field: string;
|
|
115
|
+
expected: string | null;
|
|
116
|
+
actual: string | null;
|
|
117
|
+
}[];
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Ordered outbound call matcher for replay
|
|
121
|
+
*/
|
|
122
|
+
export declare class OutboundMatcher {
|
|
123
|
+
private recordedCalls;
|
|
124
|
+
private currentIndex;
|
|
125
|
+
private mode;
|
|
126
|
+
private traceDir;
|
|
127
|
+
private divergences;
|
|
128
|
+
private logger;
|
|
129
|
+
constructor(recordedCalls: RecordedOutboundCall[], mode: ReplayMode, traceDir: string, logger?: ReplayLogger);
|
|
130
|
+
/**
|
|
131
|
+
* Set the logger for structured replay logging
|
|
132
|
+
*/
|
|
133
|
+
setLogger(logger: ReplayLogger): void;
|
|
134
|
+
/**
|
|
135
|
+
* Get all recorded divergences (for explain mode)
|
|
136
|
+
*/
|
|
137
|
+
getDivergences(): OutboundDivergence[];
|
|
138
|
+
/**
|
|
139
|
+
* Get current call index
|
|
140
|
+
*/
|
|
141
|
+
getCurrentIndex(): number;
|
|
142
|
+
/**
|
|
143
|
+
* Get total recorded calls
|
|
144
|
+
*/
|
|
145
|
+
getTotalCalls(): number;
|
|
146
|
+
/**
|
|
147
|
+
* Match the next outbound call against the recorded sequence
|
|
148
|
+
*
|
|
149
|
+
* In strict mode: throws OutboundRequestMismatch on any mismatch
|
|
150
|
+
* In explain mode: emits divergence and returns best-effort match
|
|
151
|
+
*/
|
|
152
|
+
matchNext(actual: OutboundCallSignature): RecordedOutboundCall;
|
|
153
|
+
/**
|
|
154
|
+
* Check if all recorded calls have been matched
|
|
155
|
+
*/
|
|
156
|
+
isComplete(): boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Get summary of unmatched calls (calls that were recorded but not replayed)
|
|
159
|
+
*/
|
|
160
|
+
getUnmatchedCalls(): RecordedOutboundCall[];
|
|
161
|
+
/**
|
|
162
|
+
* Finalize replay and check for missing outbound calls.
|
|
163
|
+
* Should be called at the end of replay to detect any recorded calls that never happened.
|
|
164
|
+
*
|
|
165
|
+
* In strict mode: throws MissingOutboundCall[] if any calls are missing
|
|
166
|
+
* In explain mode: emits divergences and returns the missing calls
|
|
167
|
+
*
|
|
168
|
+
* @returns Array of MissingOutboundCall failures (empty if all calls matched)
|
|
169
|
+
*/
|
|
170
|
+
finalize(): MissingOutboundCall[];
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Parse recorded outbound calls from trace events
|
|
174
|
+
*/
|
|
175
|
+
export declare function parseRecordedOutboundCalls(events: any[]): RecordedOutboundCall[];
|
|
176
|
+
/**
|
|
177
|
+
* Format OutboundRequestMismatch for CLI output
|
|
178
|
+
*/
|
|
179
|
+
export declare function formatOutboundMismatch(failure: OutboundRequestMismatch): string;
|
|
180
|
+
/**
|
|
181
|
+
* Format UnexpectedOutboundCall for CLI output
|
|
182
|
+
*/
|
|
183
|
+
export declare function formatUnexpectedOutboundCall(failure: UnexpectedOutboundCall): string;
|
|
184
|
+
/**
|
|
185
|
+
* Format MissingOutboundCall for CLI output
|
|
186
|
+
*/
|
|
187
|
+
export declare function formatMissingOutboundCall(failure: MissingOutboundCall): string;
|
|
188
|
+
/**
|
|
189
|
+
* Format any outbound call failure for CLI output
|
|
190
|
+
*/
|
|
191
|
+
export declare function formatOutboundCallFailure(failure: OutboundCallFailure): string;
|
|
192
|
+
//# sourceMappingURL=outbound-matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-matcher.d.ts","sourceRoot":"","sources":["../src/outbound-matcher.ts"],"names":[],"mappings":"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"}
|