mcp-event-intelligence 0.4.0 → 0.6.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 +100 -0
- package/README.md +107 -26
- package/SECURITY.md +4 -4
- package/bin/mcp-event-intelligence.mjs +3 -3
- package/dist/src/composite/engine.d.ts +14 -4
- package/dist/src/composite/engine.js +533 -208
- package/dist/src/composite/matching.d.ts +1 -5
- package/dist/src/composite/matching.js +45 -54
- package/dist/src/composite/runtimeSupport.js +10 -6
- package/dist/src/composite/store.d.ts +38 -4
- package/dist/src/composite/temporal.d.ts +0 -17
- package/dist/src/composite/temporal.js +0 -266
- package/dist/src/intelligenceProtocol/activationSchemas.d.ts +38 -3
- package/dist/src/intelligenceProtocol/activationSchemas.js +5 -3
- package/dist/src/intelligenceProtocol/index.d.ts +12 -9
- package/dist/src/intelligenceProtocol/index.js +6 -5
- package/dist/src/intelligenceProtocol/lifecycle.js +2 -10
- package/dist/src/intelligenceProtocol/operatorRegistry.d.ts +300 -0
- package/dist/src/intelligenceProtocol/operatorRegistry.js +338 -0
- package/dist/src/intelligenceProtocol/patternV2Schemas.d.ts +188 -0
- package/dist/src/intelligenceProtocol/patternV2Schemas.js +801 -0
- package/dist/src/intelligenceProtocol/schemas.d.ts +4 -189
- package/dist/src/intelligenceProtocol/schemas.js +3 -97
- package/dist/src/intelligenceProtocol/triggerPlanSchemas.d.ts +114 -38
- package/dist/src/intelligenceProtocol/triggerPlanSchemas.js +27 -18
- package/dist/src/intelligenceProtocol/triggerSchemas.d.ts +268 -213
- package/dist/src/intelligenceProtocol/triggerSchemas.js +155 -169
- package/dist/src/intelligenceProtocol/version.d.ts +2 -2
- package/dist/src/intelligenceProtocol/version.js +2 -2
- package/dist/src/mcpEvents/consumer.d.ts +1 -0
- package/dist/src/mcpEvents/consumer.js +1 -0
- package/dist/src/patternV2/evaluator.d.ts +51 -0
- package/dist/src/patternV2/evaluator.js +986 -0
- package/dist/src/semantic/types.d.ts +6 -0
- package/dist/src/semantic/typesafeJevEvaluator.d.ts +1 -0
- package/dist/src/semantic/typesafeJevEvaluator.js +6 -0
- package/package.json +9 -3
- package/scripts/host-integration.d.mts +54 -4
- package/scripts/host-integration.mjs +25 -4
- package/scripts/lib/activation-hydrator.mjs +13 -12
- package/scripts/lib/composite-event-consumer.mjs +10 -28
- package/scripts/lib/composite-wake-coordinator.mjs +19 -19
- package/scripts/lib/derived-contract-registry.mjs +4 -4
- package/scripts/lib/derived-event-coordinator.mjs +69 -6
- package/scripts/lib/local-event-intelligence-runtime.mjs +35 -3
- package/scripts/lib/mcp-events-client.mjs +97 -55
- package/scripts/lib/mcp-events-compatibility.mjs +54 -0
- package/scripts/lib/persistent-event-store.mjs +228 -61
- package/scripts/lib/store-capabilities.mjs +88 -0
- package/scripts/lib/store-conformance.mjs +127 -0
- package/scripts/lib/temporal-deadline-scheduler.mjs +3 -3
- package/scripts/lib/trigger-control-plane.mjs +202 -53
- package/scripts/lib/trigger-inspector.mjs +50 -147
- package/scripts/lib/trigger-planner.mjs +141 -52
- package/scripts/lib/wake-retry-scheduler.mjs +2 -19
- package/scripts/mcp-stdio-server.mjs +46 -61
- package/scripts/service.mjs +19 -138
- package/server.json +4 -4
- package/dist/src/intelligenceProtocol/invariants.d.ts +0 -3
- package/dist/src/intelligenceProtocol/invariants.js +0 -36
- package/dist/src/intelligenceProtocol/mapper.d.ts +0 -14
- package/dist/src/intelligenceProtocol/mapper.js +0 -28
- package/scripts/lib/event-processor.mjs +0 -624
- package/scripts/lib/http-wake-deliverer.mjs +0 -31
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CompositeTriggerEngine,
|
|
3
3
|
} from '../../dist/src/composite/engine.js';
|
|
4
|
-
import {
|
|
5
|
-
evaluateTemporalConditions,
|
|
6
|
-
} from '../../dist/src/composite/temporal.js';
|
|
7
4
|
import {
|
|
8
5
|
parseCompositeTriggerDefinition,
|
|
9
6
|
parseCorrelatableEvent,
|
|
@@ -11,8 +8,19 @@ import {
|
|
|
11
8
|
} from '../../dist/src/intelligenceProtocol/index.js';
|
|
12
9
|
|
|
13
10
|
function latest(records) {
|
|
11
|
+
const rank = (record) => {
|
|
12
|
+
if (record.status === 'fired') return 5;
|
|
13
|
+
if (record.status === 'emitted') return 4;
|
|
14
|
+
if (record.status === 'matched') return 3;
|
|
15
|
+
if (record.status === 'partial' && record.patternState?.role === 'match') return 2;
|
|
16
|
+
if (record.status === 'partial') return 1;
|
|
17
|
+
return 0;
|
|
18
|
+
};
|
|
19
|
+
|
|
14
20
|
return [...records].sort(
|
|
15
|
-
(a, b) =>
|
|
21
|
+
(a, b) =>
|
|
22
|
+
Date.parse(b.updatedAt) - Date.parse(a.updatedAt) ||
|
|
23
|
+
rank(b) - rank(a),
|
|
16
24
|
)[0] ?? null;
|
|
17
25
|
}
|
|
18
26
|
|
|
@@ -32,12 +40,6 @@ function sourceRef(event) {
|
|
|
32
40
|
};
|
|
33
41
|
}
|
|
34
42
|
|
|
35
|
-
function baseExpressionRefs(expression) {
|
|
36
|
-
return expression.kind === 'count'
|
|
37
|
-
? [expression.ref]
|
|
38
|
-
: expression.refs;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
43
|
function clauseState(definition, match) {
|
|
42
44
|
const seen = new Map();
|
|
43
45
|
for (const event of match?.sourceEvents ?? []) {
|
|
@@ -48,63 +50,19 @@ function clauseState(definition, match) {
|
|
|
48
50
|
|
|
49
51
|
return definition.clauses.map((clause) => {
|
|
50
52
|
const evidence = seen.get(clause.id) ?? [];
|
|
51
|
-
const requiredCount =
|
|
52
|
-
definition.expression.kind === 'count' &&
|
|
53
|
-
definition.expression.ref === clause.id
|
|
54
|
-
? definition.expression.atLeast
|
|
55
|
-
: 1;
|
|
56
53
|
return {
|
|
57
54
|
clauseId: clause.id,
|
|
58
55
|
event: clause.event,
|
|
59
56
|
serverId: clause.serverId ?? null,
|
|
60
57
|
contractVersion: clause.contractVersion ?? null,
|
|
61
|
-
status: evidence.length
|
|
58
|
+
status: evidence.length ? 'observed' : 'waiting',
|
|
62
59
|
observedCount: evidence.length,
|
|
63
|
-
requiredCount,
|
|
64
60
|
evidence,
|
|
65
61
|
};
|
|
66
62
|
});
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
function
|
|
70
|
-
const byId = new Map(clauses.map((entry) => [entry.clauseId, entry]));
|
|
71
|
-
const expression = definition.expression;
|
|
72
|
-
if (expression.kind === 'allOf') {
|
|
73
|
-
return expression.refs.every(
|
|
74
|
-
(ref) => byId.get(ref)?.status === 'satisfied',
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
if (expression.kind === 'anyOf') {
|
|
78
|
-
return expression.refs.some(
|
|
79
|
-
(ref) => byId.get(ref)?.status === 'satisfied',
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
if (expression.kind === 'count') {
|
|
83
|
-
return (byId.get(expression.ref)?.observedCount ?? 0) >= expression.atLeast;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const ordered = [];
|
|
87
|
-
for (const ref of expression.refs) {
|
|
88
|
-
const evidence = byId.get(ref)?.evidence ?? [];
|
|
89
|
-
if (!evidence.length) return false;
|
|
90
|
-
ordered.push(evidence[0]);
|
|
91
|
-
}
|
|
92
|
-
return ordered.every(
|
|
93
|
-
(event, index) =>
|
|
94
|
-
index === 0 ||
|
|
95
|
-
Date.parse(event.occurredAt) >
|
|
96
|
-
Date.parse(ordered[index - 1].occurredAt),
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function explain({
|
|
101
|
-
definition,
|
|
102
|
-
state,
|
|
103
|
-
match,
|
|
104
|
-
clauses,
|
|
105
|
-
temporal,
|
|
106
|
-
wake,
|
|
107
|
-
}) {
|
|
65
|
+
function explain({ state, match, wake }) {
|
|
108
66
|
if (state.status === 'paused') {
|
|
109
67
|
return {
|
|
110
68
|
code: 'trigger_paused',
|
|
@@ -132,8 +90,9 @@ function explain({
|
|
|
132
90
|
|
|
133
91
|
if (!match) {
|
|
134
92
|
return {
|
|
135
|
-
code: '
|
|
136
|
-
summary:
|
|
93
|
+
code: 'waiting_for_pattern_evidence',
|
|
94
|
+
summary: 'Waiting for evidence that can satisfy the Pattern AST.',
|
|
95
|
+
patternVersion: '2',
|
|
137
96
|
};
|
|
138
97
|
}
|
|
139
98
|
|
|
@@ -156,53 +115,22 @@ function explain({
|
|
|
156
115
|
if (match.status === 'matched') {
|
|
157
116
|
return {
|
|
158
117
|
code: 'matched_waiting_delivery',
|
|
159
|
-
summary: '
|
|
118
|
+
summary: 'The Pattern AST is satisfied; effect delivery is pending or not yet marked fired.',
|
|
160
119
|
};
|
|
161
120
|
}
|
|
162
121
|
if (match.status === 'expired') {
|
|
163
122
|
return {
|
|
164
123
|
code: 'match_expired',
|
|
165
|
-
summary: 'This match can no longer satisfy the trigger.',
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const missing = clauses
|
|
170
|
-
.filter((clause) => clause.status !== 'satisfied')
|
|
171
|
-
.map((clause) => clause.clauseId);
|
|
172
|
-
|
|
173
|
-
if (!baseExpressionSatisfied(definition, clauses)) {
|
|
174
|
-
return {
|
|
175
|
-
code: 'waiting_for_event_clauses',
|
|
176
|
-
summary: `Waiting for event clauses: ${missing.join(', ') || 'expression ordering'}.`,
|
|
177
|
-
missingClauses: missing,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const blocked = temporal.conditionStates.filter(
|
|
182
|
-
(condition) => condition.status === 'blocked',
|
|
183
|
-
);
|
|
184
|
-
if (blocked.length) {
|
|
185
|
-
return {
|
|
186
|
-
code: 'temporal_blocked',
|
|
187
|
-
summary: `Temporal condition blocked: ${blocked.map((entry) => entry.conditionId).join(', ')}.`,
|
|
188
|
-
temporalConditionIds: blocked.map((entry) => entry.conditionId),
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const pending = temporal.conditionStates.filter(
|
|
193
|
-
(condition) => condition.status === 'pending',
|
|
194
|
-
);
|
|
195
|
-
if (pending.length) {
|
|
196
|
-
return {
|
|
197
|
-
code: 'waiting_for_temporal_conditions',
|
|
198
|
-
summary: `Waiting for temporal conditions: ${pending.map((entry) => entry.conditionId).join(', ')}.`,
|
|
199
|
-
temporalConditionIds: pending.map((entry) => entry.conditionId),
|
|
124
|
+
summary: 'This Pattern match can no longer satisfy the trigger.',
|
|
200
125
|
};
|
|
201
126
|
}
|
|
202
127
|
|
|
203
128
|
return {
|
|
204
|
-
code: '
|
|
205
|
-
summary: '
|
|
129
|
+
code: 'pattern_waiting',
|
|
130
|
+
summary: 'Pattern AST has buffered evidence but no new complete match is currently eligible.',
|
|
131
|
+
patternVersion: '2',
|
|
132
|
+
bufferRole: match.patternState?.role ?? null,
|
|
133
|
+
semanticDecisions: match.patternState?.semanticDecisions ?? [],
|
|
206
134
|
};
|
|
207
135
|
}
|
|
208
136
|
|
|
@@ -215,12 +143,12 @@ export class TriggerInspector {
|
|
|
215
143
|
this.now = now;
|
|
216
144
|
}
|
|
217
145
|
|
|
218
|
-
inspect({
|
|
146
|
+
async inspect({
|
|
219
147
|
triggerId,
|
|
220
148
|
version,
|
|
221
149
|
matchId,
|
|
222
150
|
}) {
|
|
223
|
-
const candidates = this.store.listTriggers()
|
|
151
|
+
const candidates = (await this.store.listTriggers())
|
|
224
152
|
.filter((definition) => definition.triggerId === triggerId)
|
|
225
153
|
.filter((definition) => !version || definition.version === version);
|
|
226
154
|
|
|
@@ -238,12 +166,12 @@ export class TriggerInspector {
|
|
|
238
166
|
})
|
|
239
167
|
)[0];
|
|
240
168
|
|
|
241
|
-
const state = this.store.getTriggerState(
|
|
169
|
+
const state = await this.store.getTriggerState(
|
|
242
170
|
definition.triggerId,
|
|
243
171
|
definition.version,
|
|
244
172
|
);
|
|
245
173
|
|
|
246
|
-
const matches = this.store.listTriggerMatches(definition.triggerId)
|
|
174
|
+
const matches = (await this.store.listTriggerMatches(definition.triggerId))
|
|
247
175
|
.filter((match) => match.triggerVersion === definition.version);
|
|
248
176
|
const match = matchId
|
|
249
177
|
? matches.find((candidate) => candidate.matchId === matchId) ?? null
|
|
@@ -256,23 +184,8 @@ export class TriggerInspector {
|
|
|
256
184
|
}
|
|
257
185
|
|
|
258
186
|
const clauses = clauseState(definition, match);
|
|
259
|
-
const
|
|
260
|
-
?
|
|
261
|
-
: {
|
|
262
|
-
outcome: definition.temporal?.length ? 'pending' : 'satisfied',
|
|
263
|
-
pendingDeadlines: [],
|
|
264
|
-
conditionStates: (definition.temporal ?? []).map((condition) => ({
|
|
265
|
-
conditionId: condition.id,
|
|
266
|
-
kind: condition.kind,
|
|
267
|
-
status: 'pending',
|
|
268
|
-
reason: 'waiting for a match',
|
|
269
|
-
})),
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const deadlines = match
|
|
273
|
-
? this.store.listTemporalDeadlines?.({
|
|
274
|
-
matchId: match.matchId,
|
|
275
|
-
}) ?? []
|
|
187
|
+
const deadlines = match && this.store.listTemporalDeadlines
|
|
188
|
+
? await this.store.listTemporalDeadlines({ matchId: match.matchId })
|
|
276
189
|
: [];
|
|
277
190
|
const pendingDeadlines = deadlines.filter(
|
|
278
191
|
(deadline) => deadline.status === 'pending',
|
|
@@ -282,25 +195,25 @@ export class TriggerInspector {
|
|
|
282
195
|
.sort()[0] ?? null;
|
|
283
196
|
|
|
284
197
|
const wake = match?.firedWakeId
|
|
285
|
-
? this.store.latestWake(match.firedWakeId)
|
|
198
|
+
? await this.store.latestWake(match.firedWakeId)
|
|
286
199
|
: null;
|
|
287
200
|
|
|
288
201
|
const derivedOutputs = match
|
|
289
|
-
? (this.store.listDerivedEvents?.({ matchId: match.matchId }) ?? [])
|
|
202
|
+
? (await this.store.listDerivedEvents?.({ matchId: match.matchId }) ?? [])
|
|
290
203
|
: [];
|
|
291
204
|
|
|
292
|
-
const
|
|
293
|
-
?
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
205
|
+
const rawHistory = match && this.store.listTriggerMatchHistory
|
|
206
|
+
? await this.store.listTriggerMatchHistory(match.matchId)
|
|
207
|
+
: (match ? [match] : []);
|
|
208
|
+
const matchHistory = rawHistory.map((record) => ({
|
|
209
|
+
status: record.status,
|
|
210
|
+
updatedAt: record.updatedAt,
|
|
211
|
+
firedWakeId: record.firedWakeId,
|
|
212
|
+
derivedEventIds: record.derivedEventIds ?? [],
|
|
213
|
+
sourceEventIds: record.sourceEvents.map(
|
|
214
|
+
(event) => event.sourceEventId,
|
|
215
|
+
),
|
|
216
|
+
}));
|
|
304
217
|
|
|
305
218
|
return {
|
|
306
219
|
trigger: {
|
|
@@ -310,8 +223,7 @@ export class TriggerInspector {
|
|
|
310
223
|
continuation: definition.continuation ?? null,
|
|
311
224
|
target: definition.target ?? null,
|
|
312
225
|
derivedEvent: definition.derivedEvent ?? null,
|
|
313
|
-
|
|
314
|
-
temporal: definition.temporal ?? [],
|
|
226
|
+
pattern: definition.pattern,
|
|
315
227
|
lifecycle: definition.lifecycle ?? {},
|
|
316
228
|
},
|
|
317
229
|
lifecycle: state,
|
|
@@ -319,17 +231,14 @@ export class TriggerInspector {
|
|
|
319
231
|
? {
|
|
320
232
|
matchId: match.matchId,
|
|
321
233
|
status: match.status,
|
|
322
|
-
|
|
234
|
+
partitionKey: match.partitionKey,
|
|
323
235
|
openedAt: match.openedAt,
|
|
324
236
|
expiresAt: match.expiresAt,
|
|
325
237
|
updatedAt: match.updatedAt,
|
|
238
|
+
patternState: match.patternState ?? null,
|
|
326
239
|
}
|
|
327
240
|
: null,
|
|
328
241
|
clauses,
|
|
329
|
-
temporal: {
|
|
330
|
-
outcome: temporal.outcome,
|
|
331
|
-
conditions: temporal.conditionStates,
|
|
332
|
-
},
|
|
333
242
|
deadlines: deadlines.map((deadline) => ({
|
|
334
243
|
deadlineId: deadline.deadlineId,
|
|
335
244
|
conditionId: deadline.conditionId,
|
|
@@ -360,15 +269,9 @@ export class TriggerInspector {
|
|
|
360
269
|
rootEvidence: record.rootEvidence,
|
|
361
270
|
})),
|
|
362
271
|
matchHistory,
|
|
272
|
+
patternState: match?.patternState ?? null,
|
|
363
273
|
},
|
|
364
|
-
why: explain({
|
|
365
|
-
definition,
|
|
366
|
-
state,
|
|
367
|
-
match,
|
|
368
|
-
clauses,
|
|
369
|
-
temporal,
|
|
370
|
-
wake,
|
|
371
|
-
}),
|
|
274
|
+
why: explain({ state, match, wake }),
|
|
372
275
|
};
|
|
373
276
|
}
|
|
374
277
|
}
|
|
@@ -623,7 +526,7 @@ export async function simulateTrigger({
|
|
|
623
526
|
order,
|
|
624
527
|
evaluatedUntil: clock.toISOString(),
|
|
625
528
|
steps,
|
|
626
|
-
inspection: inspector.inspect({
|
|
529
|
+
inspection: await inspector.inspect({
|
|
627
530
|
triggerId: definition.triggerId,
|
|
628
531
|
version: definition.version,
|
|
629
532
|
}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
canonicalJson,
|
|
3
|
+
collectPatternRefs,
|
|
3
4
|
parseCompositeTriggerDefinition,
|
|
4
5
|
parseTriggerPlanInput,
|
|
5
6
|
sha256Hex,
|
|
@@ -88,32 +89,60 @@ function validatePredicateAgainstSource(source, predicate, warnings) {
|
|
|
88
89
|
[source],
|
|
89
90
|
);
|
|
90
91
|
}
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
['startsWith', 'endsWith', 'regex'].includes(predicate.op) &&
|
|
95
|
+
node?.type &&
|
|
96
|
+
node.type !== 'string'
|
|
97
|
+
) {
|
|
98
|
+
throw sourceError(
|
|
99
|
+
'TRIGGER_PLAN_STRING_FIELD_REQUIRED',
|
|
100
|
+
`Predicate ${predicate.op} requires a string field, but ${predicate.path} is ${node.type}`,
|
|
101
|
+
[source],
|
|
102
|
+
);
|
|
103
|
+
}
|
|
91
104
|
}
|
|
92
105
|
|
|
93
|
-
function
|
|
94
|
-
if (typeof
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
function walkPatternValue(value, visitor) {
|
|
107
|
+
if (!value || typeof value !== 'object') return;
|
|
108
|
+
if (value.kind === 'field') {
|
|
109
|
+
visitor(value.ref, value.path, 'pattern value');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (value.kind === 'arithmetic') {
|
|
113
|
+
for (const arg of value.args ?? []) walkPatternValue(arg, visitor);
|
|
101
114
|
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function walkPatternNode(node, visitor) {
|
|
118
|
+
if (!node || typeof node !== 'object') return;
|
|
102
119
|
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
120
|
+
if (node.kind === 'compare') {
|
|
121
|
+
walkPatternValue(node.left, visitor);
|
|
122
|
+
if (node.right) walkPatternValue(node.right, visitor);
|
|
123
|
+
}
|
|
124
|
+
if (
|
|
125
|
+
(node.kind === 'aggregate' ||
|
|
126
|
+
node.kind === 'state' ||
|
|
127
|
+
node.kind === 'distinct') &&
|
|
128
|
+
node.path
|
|
129
|
+
) {
|
|
130
|
+
visitor(node.ref, node.path, `pattern ${node.kind}`);
|
|
131
|
+
}
|
|
132
|
+
if (node.kind === 'semantic') {
|
|
133
|
+
for (const input of node.input ?? []) {
|
|
134
|
+
const [ref, ...parts] = String(input).split('.');
|
|
135
|
+
if (ref && parts.length) {
|
|
136
|
+
visitor(ref, parts.join('.'), 'pattern semantic input');
|
|
137
|
+
}
|
|
108
138
|
}
|
|
109
|
-
return { kind: 'sequence', refs };
|
|
110
139
|
}
|
|
111
140
|
|
|
112
|
-
if (
|
|
113
|
-
|
|
141
|
+
if (Array.isArray(node.children)) {
|
|
142
|
+
for (const child of node.children) walkPatternNode(child, visitor);
|
|
114
143
|
}
|
|
115
|
-
|
|
116
|
-
|
|
144
|
+
if (node.child) walkPatternNode(node.child, visitor);
|
|
145
|
+
if (node.forbidden) walkPatternNode(node.forbidden, visitor);
|
|
117
146
|
}
|
|
118
147
|
|
|
119
148
|
function lifecycleFor(input = {}) {
|
|
@@ -127,6 +156,31 @@ function lifecycleFor(input = {}) {
|
|
|
127
156
|
};
|
|
128
157
|
}
|
|
129
158
|
|
|
159
|
+
function defaultPattern(refs) {
|
|
160
|
+
const root = refs.length === 1
|
|
161
|
+
? { kind: 'event', ref: refs[0] }
|
|
162
|
+
: {
|
|
163
|
+
kind: 'allOf',
|
|
164
|
+
children: refs.map((ref) => ({ kind: 'event', ref })),
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
version: '2',
|
|
169
|
+
root,
|
|
170
|
+
partitionBy: [],
|
|
171
|
+
selection: {
|
|
172
|
+
overlap: 'disallow',
|
|
173
|
+
afterMatch: 'skipPastLast',
|
|
174
|
+
maxMatchesPerEvent: 10,
|
|
175
|
+
},
|
|
176
|
+
execution: {
|
|
177
|
+
maxCandidates: 512,
|
|
178
|
+
maxSemanticEvaluations: 16,
|
|
179
|
+
maxBufferedEvents: 10000,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
130
184
|
export class TriggerPlanner {
|
|
131
185
|
constructor({ store }) {
|
|
132
186
|
this.store = store;
|
|
@@ -134,7 +188,7 @@ export class TriggerPlanner {
|
|
|
134
188
|
|
|
135
189
|
async plan(input) {
|
|
136
190
|
const plan = parseTriggerPlanInput(input);
|
|
137
|
-
const activeSources = this.store.listEventSources({ enabledOnly: true });
|
|
191
|
+
const activeSources = await this.store.listEventSources({ enabledOnly: true });
|
|
138
192
|
const warnings = [];
|
|
139
193
|
const resolvedSources = [];
|
|
140
194
|
const seenIds = new Set();
|
|
@@ -185,69 +239,101 @@ export class TriggerPlanner {
|
|
|
185
239
|
});
|
|
186
240
|
|
|
187
241
|
const refs = clauses.map((clause) => clause.id);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
242
|
+
|
|
243
|
+
const pattern = plan.pattern ?? defaultPattern(refs);
|
|
244
|
+
{
|
|
245
|
+
const patternRefs = collectPatternRefs(pattern.root);
|
|
246
|
+
for (const ref of patternRefs) {
|
|
247
|
+
if (!refs.includes(ref)) {
|
|
192
248
|
const error = new Error(
|
|
193
|
-
`
|
|
249
|
+
`Pattern AST references unknown event id: ${ref}`,
|
|
194
250
|
);
|
|
195
251
|
error.code = 'TRIGGER_PLAN_EVENT_REF_UNKNOWN';
|
|
196
252
|
throw error;
|
|
197
253
|
}
|
|
198
|
-
|
|
199
|
-
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const sourceForRef = (ref) => {
|
|
257
|
+
const index = clauses.findIndex((candidate) => candidate.id === ref);
|
|
258
|
+
if (index < 0) {
|
|
259
|
+
const error = new Error(
|
|
260
|
+
`Pattern AST references unknown event id: ${ref}`,
|
|
261
|
+
);
|
|
262
|
+
error.code = 'TRIGGER_PLAN_EVENT_REF_UNKNOWN';
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
return resolvedSources[index];
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
walkPatternNode(pattern.root, (ref, fieldPath, purpose) => {
|
|
200
269
|
validatePredicateAgainstSource(
|
|
201
|
-
|
|
202
|
-
{ path:
|
|
270
|
+
sourceForRef(ref),
|
|
271
|
+
{ path: fieldPath, op: 'exists', value: true },
|
|
203
272
|
warnings,
|
|
204
273
|
);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
for (const dimension of pattern.partitionBy) {
|
|
277
|
+
for (const field of dimension.fields) {
|
|
278
|
+
const clauseIndex = clauses.findIndex(
|
|
279
|
+
(candidate) => candidate.id === field.ref,
|
|
280
|
+
);
|
|
281
|
+
if (clauseIndex < 0) {
|
|
282
|
+
const error = new Error(
|
|
283
|
+
`Pattern partition references unknown event id: ${field.ref}`,
|
|
284
|
+
);
|
|
285
|
+
error.code = 'TRIGGER_PLAN_EVENT_REF_UNKNOWN';
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
validatePredicateAgainstSource(
|
|
289
|
+
resolvedSources[clauseIndex],
|
|
290
|
+
{ path: field.path, op: 'exists', value: true },
|
|
291
|
+
warnings,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
205
294
|
}
|
|
206
|
-
correlation.deterministic = {
|
|
207
|
-
kind: 'same_value',
|
|
208
|
-
fields: plan.correlateBy.map((field) => ({
|
|
209
|
-
ref: field.eventId,
|
|
210
|
-
path: field.path,
|
|
211
|
-
})),
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
if (plan.semanticCorrelation) {
|
|
215
|
-
correlation.semantic = plan.semanticCorrelation;
|
|
216
295
|
}
|
|
217
296
|
|
|
297
|
+
|
|
218
298
|
const triggerId = plan.triggerId || `planned_${(
|
|
219
299
|
await sha256Hex(canonicalJson({
|
|
220
|
-
target: plan.target,
|
|
300
|
+
target: plan.target ?? null,
|
|
221
301
|
events: plan.events,
|
|
222
|
-
|
|
223
|
-
|
|
302
|
+
pattern,
|
|
303
|
+
eventTime: plan.eventTime ?? null,
|
|
304
|
+
conditionOnly: plan.conditionOnly,
|
|
305
|
+
continuation: plan.continuation?.instruction ?? null,
|
|
224
306
|
}))
|
|
225
307
|
).slice(0, 24)}`;
|
|
226
308
|
|
|
227
309
|
const definition = parseCompositeTriggerDefinition({
|
|
228
310
|
triggerId,
|
|
229
311
|
version: plan.version,
|
|
230
|
-
description
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
312
|
+
...(plan.description || plan.continuation?.instruction
|
|
313
|
+
? {
|
|
314
|
+
description:
|
|
315
|
+
plan.description ||
|
|
316
|
+
plan.continuation.instruction.slice(0, 500),
|
|
317
|
+
}
|
|
318
|
+
: {}),
|
|
319
|
+
conditionOnly: plan.conditionOnly,
|
|
320
|
+
...(plan.continuation ? { continuation: plan.continuation } : {}),
|
|
234
321
|
clauses,
|
|
235
|
-
|
|
322
|
+
pattern,
|
|
236
323
|
withinMs: plan.withinMs,
|
|
324
|
+
...(plan.eventTime ? { eventTime: plan.eventTime } : {}),
|
|
237
325
|
lifecycle: lifecycleFor(plan.lifecycle),
|
|
238
|
-
...(
|
|
239
|
-
target: plan.target,
|
|
326
|
+
...(plan.target ? { target: plan.target } : {}),
|
|
240
327
|
});
|
|
241
328
|
|
|
242
329
|
return {
|
|
243
|
-
planVersion: '
|
|
330
|
+
planVersion: '2',
|
|
244
331
|
definition,
|
|
245
332
|
connectionIds: [...new Set(resolvedSources.map((source) => source.connectionId))],
|
|
246
333
|
resolvedSources: resolvedSources.map(sourceSummary),
|
|
247
334
|
warnings,
|
|
248
335
|
explanation: {
|
|
249
336
|
when: {
|
|
250
|
-
expression: definition.expression,
|
|
251
337
|
events: clauses.map((clause) => ({
|
|
252
338
|
id: clause.id,
|
|
253
339
|
event: clause.event,
|
|
@@ -255,12 +341,15 @@ export class TriggerPlanner {
|
|
|
255
341
|
arguments: clause.arguments,
|
|
256
342
|
where: clause.where,
|
|
257
343
|
})),
|
|
344
|
+
pattern: definition.pattern,
|
|
258
345
|
withinMs: definition.withinMs,
|
|
346
|
+
eventTime: definition.eventTime ?? { allowedLatenessMs: 0 },
|
|
259
347
|
},
|
|
260
348
|
then: {
|
|
261
|
-
|
|
349
|
+
conditionOnly: definition.conditionOnly,
|
|
350
|
+
target: definition.target ?? null,
|
|
262
351
|
instruction: definition.continuation?.instruction ?? null,
|
|
263
|
-
evidence: definition.continuation?.contextPolicy?.evidence ??
|
|
352
|
+
evidence: definition.continuation?.contextPolicy?.evidence ?? null,
|
|
264
353
|
},
|
|
265
354
|
},
|
|
266
355
|
};
|
|
@@ -2,13 +2,11 @@ export class WakeRetryScheduler {
|
|
|
2
2
|
constructor({
|
|
3
3
|
store,
|
|
4
4
|
resolveCoordinator,
|
|
5
|
-
eventProcessor = null,
|
|
6
5
|
intervalMs = 1000,
|
|
7
6
|
now = () => new Date(),
|
|
8
7
|
}) {
|
|
9
8
|
this.store = store;
|
|
10
9
|
this.resolveCoordinator = resolveCoordinator;
|
|
11
|
-
this.eventProcessor = eventProcessor;
|
|
12
10
|
this.intervalMs = Math.max(100, Number(intervalMs) || 1000);
|
|
13
11
|
this.now = now;
|
|
14
12
|
this.timer = null;
|
|
@@ -19,25 +17,10 @@ export class WakeRetryScheduler {
|
|
|
19
17
|
if (this.running) return [];
|
|
20
18
|
this.running = true;
|
|
21
19
|
try {
|
|
22
|
-
const due = this.store.listDueWakeDeliveries(this.now().toISOString());
|
|
20
|
+
const due = await this.store.listDueWakeDeliveries(this.now().toISOString());
|
|
23
21
|
const outcomes = [];
|
|
24
22
|
|
|
25
23
|
for (const delivery of due) {
|
|
26
|
-
if (delivery.sourceType === 'event') {
|
|
27
|
-
if (!this.eventProcessor || typeof this.eventProcessor.retryWake !== 'function') {
|
|
28
|
-
outcomes.push({
|
|
29
|
-
wakeId: delivery.wakeId,
|
|
30
|
-
status: 'event_processor_unconfigured',
|
|
31
|
-
});
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
const result = await this.eventProcessor.retryWake(delivery.wakeId);
|
|
35
|
-
outcomes.push({
|
|
36
|
-
wakeId: delivery.wakeId,
|
|
37
|
-
status: result.status,
|
|
38
|
-
});
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
24
|
|
|
42
25
|
const coordinator = this.resolveCoordinator(delivery.runtime);
|
|
43
26
|
if (!coordinator) {
|
|
@@ -48,7 +31,7 @@ export class WakeRetryScheduler {
|
|
|
48
31
|
continue;
|
|
49
32
|
}
|
|
50
33
|
|
|
51
|
-
const match = this.store.listTriggerMatches(delivery.triggerId)
|
|
34
|
+
const match = (await this.store.listTriggerMatches(delivery.triggerId))
|
|
52
35
|
.filter((candidate) => candidate.matchId === delivery.matchId)
|
|
53
36
|
.sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))[0];
|
|
54
37
|
|