mcp-event-intelligence 0.8.2 → 0.9.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/CHANGELOG.md +19 -0
- package/README.md +1 -1
- package/conformance/README.md +59 -0
- package/conformance/fixtures/composite-trigger.valid.json +70 -0
- package/conformance/fixtures/event-lineage.valid.json +26 -0
- package/conformance/reference-host.mjs +145 -0
- package/package.json +15 -3
- package/scripts/embedded-host-kit.mjs +4 -0
- package/scripts/host-conformance.d.mts +75 -0
- package/scripts/host-conformance.mjs +238 -0
- package/scripts/host-integration.d.mts +5 -0
- package/scripts/host-integration.mjs +23 -4
- package/scripts/lib/composite-event-consumer.mjs +28 -0
- package/scripts/lib/composite-wake-coordinator.mjs +64 -0
- package/scripts/lib/local-event-intelligence-runtime.mjs +39 -0
- package/scripts/lib/mcp-events-client.mjs +36 -0
- package/scripts/lib/observability.d.mts +77 -0
- package/scripts/lib/observability.mjs +134 -0
- package/scripts/lib/temporal-deadline-scheduler.mjs +10 -0
- package/scripts/lib/trigger-control-plane.mjs +26 -0
- package/scripts/lib/wake-retry-scheduler.mjs +10 -0
- package/server.json +2 -2
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
const REPORT_SCHEMA = 'event-intelligence.host-conformance.v1';
|
|
2
|
+
|
|
3
|
+
function assertFunction(value, name) {
|
|
4
|
+
if (typeof value !== 'function') {
|
|
5
|
+
throw new Error(`Host conformance adapter requires ${name}()`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function validateHarness(harness) {
|
|
10
|
+
if (!harness || typeof harness !== 'object') {
|
|
11
|
+
throw new Error('createHarness() must return a harness object');
|
|
12
|
+
}
|
|
13
|
+
for (const method of [
|
|
14
|
+
'createTrigger',
|
|
15
|
+
'emitEvent',
|
|
16
|
+
'deliveries',
|
|
17
|
+
'restart',
|
|
18
|
+
'inspectTrigger',
|
|
19
|
+
'close',
|
|
20
|
+
]) {
|
|
21
|
+
assertFunction(harness[method], method);
|
|
22
|
+
}
|
|
23
|
+
return harness;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function deliveryCount(rows, triggerId) {
|
|
27
|
+
return rows.filter((row) => row?.triggerId === triggerId).length;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function runCase(id, execute) {
|
|
31
|
+
const startedAt = Date.now();
|
|
32
|
+
try {
|
|
33
|
+
const detail = await execute();
|
|
34
|
+
return Object.freeze({
|
|
35
|
+
id,
|
|
36
|
+
status: 'pass',
|
|
37
|
+
durationMs: Date.now() - startedAt,
|
|
38
|
+
...(detail ? { detail } : {}),
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return Object.freeze({
|
|
42
|
+
id,
|
|
43
|
+
status: 'fail',
|
|
44
|
+
durationMs: Date.now() - startedAt,
|
|
45
|
+
error: error instanceof Error ? error.message : String(error),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function requireCondition(condition, message) {
|
|
51
|
+
if (!condition) throw new Error(message);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Run the runtime-neutral EI host contract against an isolated host harness.
|
|
56
|
+
*
|
|
57
|
+
* The adapter owns all runtime-specific translation. The kit only uses a
|
|
58
|
+
* canonical value-change source and inspects externally visible deliveries,
|
|
59
|
+
* persisted trigger state and structured observability events.
|
|
60
|
+
*/
|
|
61
|
+
export async function runHostConformance(
|
|
62
|
+
adapter,
|
|
63
|
+
{ throwOnFailure = false } = {},
|
|
64
|
+
) {
|
|
65
|
+
if (!adapter || typeof adapter !== 'object') {
|
|
66
|
+
throw new Error('Host conformance adapter is required');
|
|
67
|
+
}
|
|
68
|
+
assertFunction(adapter.createHarness, 'createHarness');
|
|
69
|
+
|
|
70
|
+
const observed = [];
|
|
71
|
+
const harness = validateHarness(await adapter.createHarness({
|
|
72
|
+
observability(event) {
|
|
73
|
+
observed.push(event);
|
|
74
|
+
},
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
const results = [];
|
|
78
|
+
try {
|
|
79
|
+
results.push(await runCase('core.once-threshold', async () => {
|
|
80
|
+
await harness.createTrigger({
|
|
81
|
+
triggerId: 'conformance-once',
|
|
82
|
+
threshold: 1000,
|
|
83
|
+
oneShot: true,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await harness.emitEvent({
|
|
87
|
+
eventId: 'once-below',
|
|
88
|
+
value: 999,
|
|
89
|
+
secretProbe: 'ei-conformance-secret-probe',
|
|
90
|
+
});
|
|
91
|
+
requireCondition(
|
|
92
|
+
deliveryCount(await harness.deliveries(), 'conformance-once') === 0,
|
|
93
|
+
'below-threshold event produced a delivery',
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
await harness.emitEvent({
|
|
97
|
+
eventId: 'once-match',
|
|
98
|
+
value: 1001,
|
|
99
|
+
secretProbe: 'ei-conformance-secret-probe',
|
|
100
|
+
});
|
|
101
|
+
requireCondition(
|
|
102
|
+
deliveryCount(await harness.deliveries(), 'conformance-once') === 1,
|
|
103
|
+
'matching event did not produce exactly one delivery',
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
results.push(await runCase('core.replay-dedup', async () => {
|
|
109
|
+
await harness.createTrigger({
|
|
110
|
+
triggerId: 'conformance-replay',
|
|
111
|
+
threshold: 1000,
|
|
112
|
+
oneShot: false,
|
|
113
|
+
maxFirings: 2,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await harness.emitEvent({
|
|
117
|
+
eventId: 'replay-same-id',
|
|
118
|
+
value: 1100,
|
|
119
|
+
secretProbe: 'ei-conformance-secret-probe',
|
|
120
|
+
});
|
|
121
|
+
await harness.emitEvent({
|
|
122
|
+
eventId: 'replay-same-id',
|
|
123
|
+
value: 1100,
|
|
124
|
+
secretProbe: 'ei-conformance-secret-probe',
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
requireCondition(
|
|
128
|
+
deliveryCount(await harness.deliveries(), 'conformance-replay') === 1,
|
|
129
|
+
'replayed event produced a duplicate delivery on a persistent trigger',
|
|
130
|
+
);
|
|
131
|
+
const state = await harness.inspectTrigger('conformance-replay');
|
|
132
|
+
requireCondition(
|
|
133
|
+
Number(state?.fireCount) === 1,
|
|
134
|
+
`replayed event advanced fireCount to ${state?.fireCount}`,
|
|
135
|
+
);
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
results.push(await runCase('core.restart-durability', async () => {
|
|
139
|
+
await harness.createTrigger({
|
|
140
|
+
triggerId: 'conformance-restart',
|
|
141
|
+
threshold: 1000,
|
|
142
|
+
oneShot: false,
|
|
143
|
+
maxFirings: 2,
|
|
144
|
+
});
|
|
145
|
+
await harness.restart();
|
|
146
|
+
|
|
147
|
+
await harness.emitEvent({
|
|
148
|
+
eventId: 'restart-1',
|
|
149
|
+
value: 1200,
|
|
150
|
+
secretProbe: 'ei-conformance-secret-probe',
|
|
151
|
+
});
|
|
152
|
+
requireCondition(
|
|
153
|
+
deliveryCount(await harness.deliveries(), 'conformance-restart') === 1,
|
|
154
|
+
'trigger did not survive runtime restart',
|
|
155
|
+
);
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
results.push(await runCase('core.max-firings', async () => {
|
|
159
|
+
await harness.emitEvent({
|
|
160
|
+
eventId: 'restart-2',
|
|
161
|
+
value: 1300,
|
|
162
|
+
secretProbe: 'ei-conformance-secret-probe',
|
|
163
|
+
});
|
|
164
|
+
await harness.emitEvent({
|
|
165
|
+
eventId: 'restart-3',
|
|
166
|
+
value: 1400,
|
|
167
|
+
secretProbe: 'ei-conformance-secret-probe',
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
requireCondition(
|
|
171
|
+
deliveryCount(await harness.deliveries(), 'conformance-restart') === 2,
|
|
172
|
+
'maxFirings=2 did not cap external deliveries at two',
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const state = await harness.inspectTrigger('conformance-restart');
|
|
176
|
+
requireCondition(Boolean(state), 'trigger state unavailable after firing');
|
|
177
|
+
requireCondition(
|
|
178
|
+
Number(state.fireCount) === 2,
|
|
179
|
+
`expected fireCount=2, got ${state.fireCount}`,
|
|
180
|
+
);
|
|
181
|
+
}));
|
|
182
|
+
|
|
183
|
+
results.push(await runCase('observability.required-events', async () => {
|
|
184
|
+
const names = new Set(observed.map((event) => event?.event));
|
|
185
|
+
for (const name of [
|
|
186
|
+
'ei.trigger.created',
|
|
187
|
+
'ei.match.matched',
|
|
188
|
+
'ei.wake.delivered',
|
|
189
|
+
]) {
|
|
190
|
+
requireCondition(
|
|
191
|
+
names.has(name),
|
|
192
|
+
`missing required observability event ${name}`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const serialized = JSON.stringify(observed);
|
|
197
|
+
requireCondition(
|
|
198
|
+
!serialized.includes('ei-conformance-secret-probe'),
|
|
199
|
+
'observability leaked event payload content',
|
|
200
|
+
);
|
|
201
|
+
}));
|
|
202
|
+
} finally {
|
|
203
|
+
await harness.close();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const failed = results.filter((result) => result.status === 'fail');
|
|
207
|
+
const report = Object.freeze({
|
|
208
|
+
schema: REPORT_SCHEMA,
|
|
209
|
+
adapter: String(adapter.name ?? 'unnamed-host'),
|
|
210
|
+
passed: failed.length === 0,
|
|
211
|
+
summary: Object.freeze({
|
|
212
|
+
total: results.length,
|
|
213
|
+
passed: results.length - failed.length,
|
|
214
|
+
failed: failed.length,
|
|
215
|
+
}),
|
|
216
|
+
results: Object.freeze(results),
|
|
217
|
+
observability: Object.freeze({
|
|
218
|
+
captured: observed.length,
|
|
219
|
+
eventNames: Object.freeze([...new Set(
|
|
220
|
+
observed.map((event) => event?.event).filter(Boolean),
|
|
221
|
+
)].sort()),
|
|
222
|
+
}),
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (throwOnFailure && !report.passed) {
|
|
226
|
+
const error = new Error(
|
|
227
|
+
`Event Intelligence host conformance failed: ${failed
|
|
228
|
+
.map((item) => item.id)
|
|
229
|
+
.join(', ')}`,
|
|
230
|
+
);
|
|
231
|
+
error.report = report;
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return report;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export const HOST_CONFORMANCE_REPORT_SCHEMA = REPORT_SCHEMA;
|
|
@@ -2,6 +2,9 @@ import type {
|
|
|
2
2
|
ActivationEnvelope,
|
|
3
3
|
TriggerPlanInput,
|
|
4
4
|
} from '../dist/src/intelligenceProtocol/index.js';
|
|
5
|
+
import type {
|
|
6
|
+
EventIntelligenceObservabilitySink,
|
|
7
|
+
} from './lib/observability.d.mts';
|
|
5
8
|
|
|
6
9
|
export interface HostMcpClientLike {
|
|
7
10
|
request(
|
|
@@ -130,6 +133,8 @@ export interface EventIntelligenceHostOptions {
|
|
|
130
133
|
/** Optional runtime-specific dispatchers. */
|
|
131
134
|
wakeHandlers?: Map<string, HostWakeHandler> | Record<string, HostWakeHandler>;
|
|
132
135
|
semanticEvaluator?: unknown;
|
|
136
|
+
/** Runtime-neutral structured operational events. Sink failures never affect EI semantics. */
|
|
137
|
+
observability?: EventIntelligenceObservabilitySink;
|
|
133
138
|
}
|
|
134
139
|
|
|
135
140
|
export interface EventIntelligenceScopedHost {
|
|
@@ -127,6 +127,7 @@ export async function createEventIntelligenceHost({
|
|
|
127
127
|
wake,
|
|
128
128
|
wakeHandlers = {},
|
|
129
129
|
semanticEvaluator,
|
|
130
|
+
observability,
|
|
130
131
|
} = {}) {
|
|
131
132
|
const explicitConnections = mcpClients.map(normalizeMcpConnection);
|
|
132
133
|
const runtime = await createLocalEventIntelligenceRuntime({
|
|
@@ -140,7 +141,9 @@ export async function createEventIntelligenceHost({
|
|
|
140
141
|
wakeHandlers,
|
|
141
142
|
semanticEvaluator,
|
|
142
143
|
store,
|
|
144
|
+
observability,
|
|
143
145
|
});
|
|
146
|
+
const observer = runtime.observability;
|
|
144
147
|
|
|
145
148
|
const registryManaged = new Set();
|
|
146
149
|
let unsubscribe = null;
|
|
@@ -212,6 +215,21 @@ export async function createEventIntelligenceHost({
|
|
|
212
215
|
}
|
|
213
216
|
}
|
|
214
217
|
|
|
218
|
+
await Promise.all(outcomes.map((outcome) => observer.emit({
|
|
219
|
+
event: 'ei.source.registry_update',
|
|
220
|
+
level:
|
|
221
|
+
outcome.status === 'error'
|
|
222
|
+
? 'error'
|
|
223
|
+
: outcome.status === 'ignored_no_events' ||
|
|
224
|
+
outcome.status === 'already_attached'
|
|
225
|
+
? 'debug'
|
|
226
|
+
: 'info',
|
|
227
|
+
connectionId: outcome.connectionId,
|
|
228
|
+
status: outcome.status,
|
|
229
|
+
...(outcome.error ? { error: new Error(outcome.error) } : {}),
|
|
230
|
+
...(outcome.events ? { metadata: { eventNames: outcome.events } } : {}),
|
|
231
|
+
})));
|
|
232
|
+
|
|
215
233
|
return outcomes;
|
|
216
234
|
};
|
|
217
235
|
|
|
@@ -241,10 +259,11 @@ export async function createEventIntelligenceHost({
|
|
|
241
259
|
if (typeof mcpRegistry.subscribe === 'function') {
|
|
242
260
|
const maybeUnsubscribe = mcpRegistry.subscribe(() => {
|
|
243
261
|
refreshMcpRegistry().catch((error) => {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
262
|
+
void observer.emit({
|
|
263
|
+
event: 'ei.source.refresh_failed',
|
|
264
|
+
level: 'error',
|
|
265
|
+
error,
|
|
266
|
+
});
|
|
248
267
|
});
|
|
249
268
|
});
|
|
250
269
|
if (typeof maybeUnsubscribe === 'function') {
|
|
@@ -10,6 +10,7 @@ export class CompositeEventConsumer {
|
|
|
10
10
|
wakeCoordinators = null,
|
|
11
11
|
derivedEventCoordinator = null,
|
|
12
12
|
maxDerivedDepth = 16,
|
|
13
|
+
observability = null,
|
|
13
14
|
}) {
|
|
14
15
|
this.store = store;
|
|
15
16
|
this.triggerEngine = triggerEngine;
|
|
@@ -19,6 +20,7 @@ export class CompositeEventConsumer {
|
|
|
19
20
|
: new Map();
|
|
20
21
|
this.derivedEventCoordinator = derivedEventCoordinator;
|
|
21
22
|
this.maxDerivedDepth = Math.max(1, Number(maxDerivedDepth) || 16);
|
|
23
|
+
this.observability = observability;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
coordinatorFor(runtime) {
|
|
@@ -58,6 +60,15 @@ export class CompositeEventConsumer {
|
|
|
58
60
|
for (const result of results) {
|
|
59
61
|
if (!result.matched || !result.match) continue;
|
|
60
62
|
|
|
63
|
+
await this.observability?.emit({
|
|
64
|
+
event: 'ei.match.matched',
|
|
65
|
+
level: 'info',
|
|
66
|
+
traceId: event.traceId,
|
|
67
|
+
triggerId: result.match.triggerId,
|
|
68
|
+
matchId: result.match.matchId,
|
|
69
|
+
status: result.match.status,
|
|
70
|
+
});
|
|
71
|
+
|
|
61
72
|
const definition = (await this.store.listTriggers()).find(
|
|
62
73
|
(candidate) =>
|
|
63
74
|
candidate.triggerId === result.match.triggerId &&
|
|
@@ -72,6 +83,14 @@ export class CompositeEventConsumer {
|
|
|
72
83
|
status: 'trigger_definition_missing',
|
|
73
84
|
runtimeReceiptId: null,
|
|
74
85
|
});
|
|
86
|
+
await this.observability?.emit({
|
|
87
|
+
event: 'ei.match.definition_missing',
|
|
88
|
+
level: 'error',
|
|
89
|
+
traceId: event.traceId,
|
|
90
|
+
triggerId: result.triggerId,
|
|
91
|
+
matchId: result.match.matchId,
|
|
92
|
+
status: 'trigger_definition_missing',
|
|
93
|
+
});
|
|
75
94
|
continue;
|
|
76
95
|
}
|
|
77
96
|
|
|
@@ -131,6 +150,15 @@ export class CompositeEventConsumer {
|
|
|
131
150
|
status: 'runtime_unconfigured',
|
|
132
151
|
runtimeReceiptId: null,
|
|
133
152
|
});
|
|
153
|
+
await this.observability?.emit({
|
|
154
|
+
event: 'ei.wake.runtime_unconfigured',
|
|
155
|
+
level: 'warn',
|
|
156
|
+
traceId: event.traceId,
|
|
157
|
+
triggerId: result.triggerId,
|
|
158
|
+
matchId: result.match.matchId,
|
|
159
|
+
status: 'runtime_unconfigured',
|
|
160
|
+
metadata: { runtime: definition.target.runtime },
|
|
161
|
+
});
|
|
134
162
|
continue;
|
|
135
163
|
}
|
|
136
164
|
|
|
@@ -31,6 +31,7 @@ export class CompositeWakeCoordinator {
|
|
|
31
31
|
maxAttempts = 5,
|
|
32
32
|
retryBaseDelayMs = 1000,
|
|
33
33
|
retryMaxDelayMs = 60000,
|
|
34
|
+
observability = null,
|
|
34
35
|
}) {
|
|
35
36
|
this.store = store;
|
|
36
37
|
this.triggerEngine = triggerEngine;
|
|
@@ -45,6 +46,7 @@ export class CompositeWakeCoordinator {
|
|
|
45
46
|
this.retryBaseDelayMs,
|
|
46
47
|
Number(retryMaxDelayMs) || 60000,
|
|
47
48
|
);
|
|
49
|
+
this.observability = observability;
|
|
48
50
|
|
|
49
51
|
for (const method of [
|
|
50
52
|
'ensureWakeDelivery',
|
|
@@ -101,6 +103,16 @@ export class CompositeWakeCoordinator {
|
|
|
101
103
|
composite: true,
|
|
102
104
|
},
|
|
103
105
|
});
|
|
106
|
+
await this.observability?.emit({
|
|
107
|
+
event: 'ei.wake.delivered',
|
|
108
|
+
level: 'info',
|
|
109
|
+
traceId: delivered.traceId,
|
|
110
|
+
triggerId: match.triggerId,
|
|
111
|
+
matchId: match.matchId,
|
|
112
|
+
wakeId,
|
|
113
|
+
status: 'wake_delivered',
|
|
114
|
+
metadata: { recoveredFromDeliveryState: true },
|
|
115
|
+
});
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
if (match.status !== 'fired') {
|
|
@@ -193,6 +205,15 @@ export class CompositeWakeCoordinator {
|
|
|
193
205
|
composite: true,
|
|
194
206
|
},
|
|
195
207
|
});
|
|
208
|
+
await this.observability?.emit({
|
|
209
|
+
event: 'ei.wake.queued',
|
|
210
|
+
level: 'debug',
|
|
211
|
+
traceId: queued.traceId,
|
|
212
|
+
triggerId: match.triggerId,
|
|
213
|
+
matchId: match.matchId,
|
|
214
|
+
wakeId,
|
|
215
|
+
status: 'wake_queued',
|
|
216
|
+
});
|
|
196
217
|
}
|
|
197
218
|
return {
|
|
198
219
|
status: 'wake_queued',
|
|
@@ -272,6 +293,15 @@ export class CompositeWakeCoordinator {
|
|
|
272
293
|
composite: true,
|
|
273
294
|
},
|
|
274
295
|
});
|
|
296
|
+
await this.observability?.emit({
|
|
297
|
+
event: 'ei.wake.queued',
|
|
298
|
+
level: 'debug',
|
|
299
|
+
traceId: queued.traceId,
|
|
300
|
+
triggerId: match.triggerId,
|
|
301
|
+
matchId: match.matchId,
|
|
302
|
+
wakeId,
|
|
303
|
+
status: 'wake_queued',
|
|
304
|
+
});
|
|
275
305
|
}
|
|
276
306
|
|
|
277
307
|
try {
|
|
@@ -322,6 +352,17 @@ export class CompositeWakeCoordinator {
|
|
|
322
352
|
composite: true,
|
|
323
353
|
},
|
|
324
354
|
});
|
|
355
|
+
await this.observability?.emit({
|
|
356
|
+
event: 'ei.wake.delivered',
|
|
357
|
+
level: 'info',
|
|
358
|
+
traceId: delivered.traceId,
|
|
359
|
+
triggerId: match.triggerId,
|
|
360
|
+
matchId: match.matchId,
|
|
361
|
+
wakeId,
|
|
362
|
+
status: 'wake_delivered',
|
|
363
|
+
attempt: delivery?.attemptCount ?? claim.attemptCount,
|
|
364
|
+
metadata: { duplicate: receipt.duplicate === true },
|
|
365
|
+
});
|
|
325
366
|
|
|
326
367
|
await this.triggerEngine.markFired(match.matchId, wakeId);
|
|
327
368
|
|
|
@@ -365,6 +406,18 @@ export class CompositeWakeCoordinator {
|
|
|
365
406
|
composite: true,
|
|
366
407
|
},
|
|
367
408
|
});
|
|
409
|
+
await this.observability?.emit({
|
|
410
|
+
event: 'ei.wake.retry_scheduled',
|
|
411
|
+
level: 'warn',
|
|
412
|
+
traceId: queued.traceId,
|
|
413
|
+
triggerId: match.triggerId,
|
|
414
|
+
matchId: match.matchId,
|
|
415
|
+
wakeId,
|
|
416
|
+
status: 'retry_scheduled',
|
|
417
|
+
attempt: delivery?.attemptCount ?? claim.attemptCount,
|
|
418
|
+
nextAttemptAt: delivery?.nextAttemptAt ?? undefined,
|
|
419
|
+
error,
|
|
420
|
+
});
|
|
368
421
|
return {
|
|
369
422
|
status: 'retry_scheduled',
|
|
370
423
|
wake: persistedQueued,
|
|
@@ -398,6 +451,17 @@ export class CompositeWakeCoordinator {
|
|
|
398
451
|
composite: true,
|
|
399
452
|
},
|
|
400
453
|
});
|
|
454
|
+
await this.observability?.emit({
|
|
455
|
+
event: 'ei.wake.dead_letter',
|
|
456
|
+
level: 'error',
|
|
457
|
+
traceId: deadLetter.traceId,
|
|
458
|
+
triggerId: match.triggerId,
|
|
459
|
+
matchId: match.matchId,
|
|
460
|
+
wakeId,
|
|
461
|
+
status: 'dead_letter',
|
|
462
|
+
attempt: delivery?.attemptCount ?? claim.attemptCount,
|
|
463
|
+
error,
|
|
464
|
+
});
|
|
401
465
|
return {
|
|
402
466
|
status: 'dead_letter',
|
|
403
467
|
wake: deadLetter,
|
|
@@ -48,6 +48,9 @@ import {
|
|
|
48
48
|
import {
|
|
49
49
|
ActivationHydrator,
|
|
50
50
|
} from './activation-hydrator.mjs';
|
|
51
|
+
import {
|
|
52
|
+
createObservabilityEmitter,
|
|
53
|
+
} from './observability.mjs';
|
|
51
54
|
|
|
52
55
|
const REQUIRED_STORE_METHODS = [
|
|
53
56
|
'putTrigger',
|
|
@@ -114,7 +117,9 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
114
117
|
wake,
|
|
115
118
|
semanticEvaluator,
|
|
116
119
|
store: providedStore,
|
|
120
|
+
observability,
|
|
117
121
|
} = {}) {
|
|
122
|
+
const observer = createObservabilityEmitter(observability);
|
|
118
123
|
const dataDir = env.DATA_DIR ?? './data';
|
|
119
124
|
const rootStore = providedStore ?? new PersistentEventStore(dataDir);
|
|
120
125
|
const restored = await initializeStore(rootStore);
|
|
@@ -212,6 +217,7 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
212
217
|
triggerEngine,
|
|
213
218
|
deliverer: createSignedRuntimeWakeDeliverer(target),
|
|
214
219
|
packetBuilder,
|
|
220
|
+
observability: observer,
|
|
215
221
|
...deliveryOptions,
|
|
216
222
|
}),
|
|
217
223
|
);
|
|
@@ -224,6 +230,7 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
224
230
|
store,
|
|
225
231
|
triggerEngine,
|
|
226
232
|
packetBuilder,
|
|
233
|
+
observability: observer,
|
|
227
234
|
...deliveryOptions,
|
|
228
235
|
deliverer: async (packet) => {
|
|
229
236
|
const activation = await activationHydrator.hydrateWake(packet.wake_id);
|
|
@@ -253,6 +260,7 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
253
260
|
store,
|
|
254
261
|
triggerEngine,
|
|
255
262
|
packetBuilder,
|
|
263
|
+
observability: observer,
|
|
256
264
|
...deliveryOptions,
|
|
257
265
|
deliverer: async (packet) => {
|
|
258
266
|
const activation = await activationHydrator.hydrateWake(packet.wake_id);
|
|
@@ -281,18 +289,21 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
281
289
|
wakeCoordinators,
|
|
282
290
|
derivedEventCoordinator,
|
|
283
291
|
maxDerivedDepth: Number(env.MAX_DERIVED_EVENT_DEPTH ?? 16),
|
|
292
|
+
observability: observer,
|
|
284
293
|
});
|
|
285
294
|
|
|
286
295
|
const temporalScheduler = new TemporalDeadlineScheduler({
|
|
287
296
|
store,
|
|
288
297
|
compositeEventConsumer,
|
|
289
298
|
intervalMs: Number(env.TEMPORAL_TICK_MS ?? 1000),
|
|
299
|
+
observability: observer,
|
|
290
300
|
});
|
|
291
301
|
temporalScheduler.start();
|
|
292
302
|
|
|
293
303
|
const triggerControl = new TriggerControlPlane({
|
|
294
304
|
store,
|
|
295
305
|
triggerEngine,
|
|
306
|
+
observability: observer,
|
|
296
307
|
});
|
|
297
308
|
const triggerInspector = new TriggerInspector({ store });
|
|
298
309
|
|
|
@@ -302,6 +313,7 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
302
313
|
store,
|
|
303
314
|
resolveCoordinator: coordinatorFor,
|
|
304
315
|
intervalMs: Number(env.WAKE_RETRY_TICK_MS ?? 1000),
|
|
316
|
+
observability: observer,
|
|
305
317
|
});
|
|
306
318
|
wakeRetryScheduler.start();
|
|
307
319
|
|
|
@@ -354,13 +366,38 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
354
366
|
principal_id: 'event-intelligence:host-mcp-client',
|
|
355
367
|
},
|
|
356
368
|
),
|
|
369
|
+
observability: observer,
|
|
357
370
|
});
|
|
358
371
|
|
|
359
372
|
const discovery = await mcpEventsClient.discoverAll();
|
|
360
373
|
mcpEventsClient.start();
|
|
361
374
|
|
|
375
|
+
for (const source of discovery) {
|
|
376
|
+
await observer.emit({
|
|
377
|
+
event: source.status === 'error'
|
|
378
|
+
? 'ei.source.discovery_failed'
|
|
379
|
+
: 'ei.source.discovered',
|
|
380
|
+
level: source.status === 'error' ? 'warn' : 'info',
|
|
381
|
+
connectionId: source.connectionId,
|
|
382
|
+
status: source.status,
|
|
383
|
+
...(source.error ? { error: new Error(source.error) } : {}),
|
|
384
|
+
...(source.events ? { metadata: { eventNames: source.events } } : {}),
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
await observer.emit({
|
|
388
|
+
event: 'ei.lifecycle.started',
|
|
389
|
+
level: 'info',
|
|
390
|
+
metadata: {
|
|
391
|
+
restoredScopes:
|
|
392
|
+
typeof rootStore.listScopeIds === 'function'
|
|
393
|
+
? await rootStore.listScopeIds()
|
|
394
|
+
: [DEFAULT_EVENT_SCOPE_ID],
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
|
|
362
398
|
return {
|
|
363
399
|
restored,
|
|
400
|
+
observability: observer,
|
|
364
401
|
storeCapabilities,
|
|
365
402
|
store: defaultContext.store,
|
|
366
403
|
evaluator,
|
|
@@ -378,6 +415,7 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
378
415
|
return [...scopeContexts.keys()].sort();
|
|
379
416
|
},
|
|
380
417
|
async close() {
|
|
418
|
+
await observer.emit({ event: 'ei.lifecycle.closing', level: 'debug' });
|
|
381
419
|
await mcpEventsClient.close();
|
|
382
420
|
const contexts = [...scopeContexts.values()];
|
|
383
421
|
await Promise.all(contexts.map((context) => context.close()));
|
|
@@ -390,6 +428,7 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
390
428
|
await store.drain();
|
|
391
429
|
}
|
|
392
430
|
}
|
|
431
|
+
await observer.emit({ event: 'ei.lifecycle.closed', level: 'info' });
|
|
393
432
|
},
|
|
394
433
|
};
|
|
395
434
|
}
|
|
@@ -286,6 +286,7 @@ export class McpEventsClientManager {
|
|
|
286
286
|
resolveScope = null,
|
|
287
287
|
connections = [],
|
|
288
288
|
now = () => new Date(),
|
|
289
|
+
observability = null,
|
|
289
290
|
}) {
|
|
290
291
|
this.store = store;
|
|
291
292
|
this.compositeEventConsumer = compositeEventConsumer;
|
|
@@ -293,6 +294,7 @@ export class McpEventsClientManager {
|
|
|
293
294
|
this.resolveScope = resolveScope;
|
|
294
295
|
this.connections = new Map();
|
|
295
296
|
this.now = now;
|
|
297
|
+
this.observability = observability;
|
|
296
298
|
this.descriptors = new Map();
|
|
297
299
|
this.profiles = new Map();
|
|
298
300
|
this.timers = new Map();
|
|
@@ -877,6 +879,15 @@ export class McpEventsClientManager {
|
|
|
877
879
|
connection.connectionId,
|
|
878
880
|
error instanceof Error ? error.message : String(error),
|
|
879
881
|
);
|
|
882
|
+
void this.observability?.emit({
|
|
883
|
+
event: 'ei.source.delivery_failed',
|
|
884
|
+
level: 'warn',
|
|
885
|
+
scopeId: connection.scopeId,
|
|
886
|
+
connectionId: connection.connectionId,
|
|
887
|
+
serverId: connection.serverId,
|
|
888
|
+
eventName: subscription.eventName,
|
|
889
|
+
error,
|
|
890
|
+
});
|
|
880
891
|
};
|
|
881
892
|
|
|
882
893
|
const onTerminated = async (detail) => {
|
|
@@ -887,6 +898,15 @@ export class McpEventsClientManager {
|
|
|
887
898
|
typeof detail === 'string' ? detail : JSON.stringify(detail),
|
|
888
899
|
);
|
|
889
900
|
}
|
|
901
|
+
void this.observability?.emit({
|
|
902
|
+
event: 'ei.source.delivery_terminated',
|
|
903
|
+
level: detail ? 'warn' : 'debug',
|
|
904
|
+
scopeId: connection.scopeId,
|
|
905
|
+
connectionId: connection.connectionId,
|
|
906
|
+
serverId: connection.serverId,
|
|
907
|
+
eventName: subscription.eventName,
|
|
908
|
+
status: 'terminated',
|
|
909
|
+
});
|
|
890
910
|
};
|
|
891
911
|
|
|
892
912
|
const open =
|
|
@@ -1054,6 +1074,14 @@ export class McpEventsClientManager {
|
|
|
1054
1074
|
} catch (error) {
|
|
1055
1075
|
const message = error instanceof Error ? error.message : String(error);
|
|
1056
1076
|
this.lastErrors.set(connection.connectionId, message);
|
|
1077
|
+
await this.observability?.emit({
|
|
1078
|
+
event: 'ei.source.poll_failed',
|
|
1079
|
+
level: 'warn',
|
|
1080
|
+
scopeId: connection.scopeId,
|
|
1081
|
+
connectionId: connection.connectionId,
|
|
1082
|
+
serverId: connection.serverId,
|
|
1083
|
+
error,
|
|
1084
|
+
});
|
|
1057
1085
|
results.push({
|
|
1058
1086
|
connectionId: connection.connectionId,
|
|
1059
1087
|
error: message,
|
|
@@ -1097,6 +1125,14 @@ export class McpEventsClientManager {
|
|
|
1097
1125
|
connection.connectionId,
|
|
1098
1126
|
error instanceof Error ? error.message : String(error),
|
|
1099
1127
|
);
|
|
1128
|
+
await this.observability?.emit({
|
|
1129
|
+
event: 'ei.source.poll_failed',
|
|
1130
|
+
level: 'warn',
|
|
1131
|
+
scopeId: connection.scopeId,
|
|
1132
|
+
connectionId: connection.connectionId,
|
|
1133
|
+
serverId: connection.serverId,
|
|
1134
|
+
error,
|
|
1135
|
+
});
|
|
1100
1136
|
} finally {
|
|
1101
1137
|
if (
|
|
1102
1138
|
this.started &&
|