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.
Files changed (64) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +107 -26
  3. package/SECURITY.md +4 -4
  4. package/bin/mcp-event-intelligence.mjs +3 -3
  5. package/dist/src/composite/engine.d.ts +14 -4
  6. package/dist/src/composite/engine.js +533 -208
  7. package/dist/src/composite/matching.d.ts +1 -5
  8. package/dist/src/composite/matching.js +45 -54
  9. package/dist/src/composite/runtimeSupport.js +10 -6
  10. package/dist/src/composite/store.d.ts +38 -4
  11. package/dist/src/composite/temporal.d.ts +0 -17
  12. package/dist/src/composite/temporal.js +0 -266
  13. package/dist/src/intelligenceProtocol/activationSchemas.d.ts +38 -3
  14. package/dist/src/intelligenceProtocol/activationSchemas.js +5 -3
  15. package/dist/src/intelligenceProtocol/index.d.ts +12 -9
  16. package/dist/src/intelligenceProtocol/index.js +6 -5
  17. package/dist/src/intelligenceProtocol/lifecycle.js +2 -10
  18. package/dist/src/intelligenceProtocol/operatorRegistry.d.ts +300 -0
  19. package/dist/src/intelligenceProtocol/operatorRegistry.js +338 -0
  20. package/dist/src/intelligenceProtocol/patternV2Schemas.d.ts +188 -0
  21. package/dist/src/intelligenceProtocol/patternV2Schemas.js +801 -0
  22. package/dist/src/intelligenceProtocol/schemas.d.ts +4 -189
  23. package/dist/src/intelligenceProtocol/schemas.js +3 -97
  24. package/dist/src/intelligenceProtocol/triggerPlanSchemas.d.ts +114 -38
  25. package/dist/src/intelligenceProtocol/triggerPlanSchemas.js +27 -18
  26. package/dist/src/intelligenceProtocol/triggerSchemas.d.ts +268 -213
  27. package/dist/src/intelligenceProtocol/triggerSchemas.js +155 -169
  28. package/dist/src/intelligenceProtocol/version.d.ts +2 -2
  29. package/dist/src/intelligenceProtocol/version.js +2 -2
  30. package/dist/src/mcpEvents/consumer.d.ts +1 -0
  31. package/dist/src/mcpEvents/consumer.js +1 -0
  32. package/dist/src/patternV2/evaluator.d.ts +51 -0
  33. package/dist/src/patternV2/evaluator.js +986 -0
  34. package/dist/src/semantic/types.d.ts +6 -0
  35. package/dist/src/semantic/typesafeJevEvaluator.d.ts +1 -0
  36. package/dist/src/semantic/typesafeJevEvaluator.js +6 -0
  37. package/package.json +9 -3
  38. package/scripts/host-integration.d.mts +54 -4
  39. package/scripts/host-integration.mjs +25 -4
  40. package/scripts/lib/activation-hydrator.mjs +13 -12
  41. package/scripts/lib/composite-event-consumer.mjs +10 -28
  42. package/scripts/lib/composite-wake-coordinator.mjs +19 -19
  43. package/scripts/lib/derived-contract-registry.mjs +4 -4
  44. package/scripts/lib/derived-event-coordinator.mjs +69 -6
  45. package/scripts/lib/local-event-intelligence-runtime.mjs +35 -3
  46. package/scripts/lib/mcp-events-client.mjs +97 -55
  47. package/scripts/lib/mcp-events-compatibility.mjs +54 -0
  48. package/scripts/lib/persistent-event-store.mjs +228 -61
  49. package/scripts/lib/store-capabilities.mjs +88 -0
  50. package/scripts/lib/store-conformance.mjs +127 -0
  51. package/scripts/lib/temporal-deadline-scheduler.mjs +3 -3
  52. package/scripts/lib/trigger-control-plane.mjs +202 -53
  53. package/scripts/lib/trigger-inspector.mjs +50 -147
  54. package/scripts/lib/trigger-planner.mjs +141 -52
  55. package/scripts/lib/wake-retry-scheduler.mjs +2 -19
  56. package/scripts/mcp-stdio-server.mjs +46 -61
  57. package/scripts/service.mjs +19 -138
  58. package/server.json +4 -4
  59. package/dist/src/intelligenceProtocol/invariants.d.ts +0 -3
  60. package/dist/src/intelligenceProtocol/invariants.js +0 -36
  61. package/dist/src/intelligenceProtocol/mapper.d.ts +0 -14
  62. package/dist/src/intelligenceProtocol/mapper.js +0 -28
  63. package/scripts/lib/event-processor.mjs +0 -624
  64. package/scripts/lib/http-wake-deliverer.mjs +0 -31
@@ -0,0 +1,986 @@
1
+ import { SemanticConditionEngine, } from '../semantic/conditionEngine.js';
2
+ import { localDateTimeToUtc, zonedParts, } from '../composite/temporal.js';
3
+ function stableValue(value) {
4
+ if (Array.isArray(value))
5
+ return value.map(stableValue);
6
+ if (value && typeof value === 'object') {
7
+ return Object.fromEntries(Object.keys(value)
8
+ .sort()
9
+ .map((key) => [
10
+ key,
11
+ stableValue(value[key]),
12
+ ]));
13
+ }
14
+ return value;
15
+ }
16
+ function durableSemanticCacheKey(context, node, candidate) {
17
+ const evaluatorIdentity = String(context.evaluator?.cacheIdentity ?? '').trim();
18
+ if (!evaluatorIdentity || !context.semanticCacheNamespace) {
19
+ return null;
20
+ }
21
+ const payload = stableValue({
22
+ schema: 'pattern-v2-semantic-cache.v1',
23
+ namespace: context.semanticCacheNamespace,
24
+ evaluator: evaluatorIdentity,
25
+ node: {
26
+ id: node.id,
27
+ refs: node.refs,
28
+ instruction: node.instruction,
29
+ input: node.input,
30
+ matchThreshold: node.matchThreshold,
31
+ rejectThreshold: node.rejectThreshold,
32
+ uncertain: node.uncertain,
33
+ },
34
+ candidate: candidate.events.map((event) => ({
35
+ clauseId: event.clauseId,
36
+ serverId: event.serverId ?? null,
37
+ sourceEventId: event.sourceEventId,
38
+ subscriptionArguments: event.subscriptionArguments ?? {},
39
+ })),
40
+ });
41
+ return JSON.stringify(payload);
42
+ }
43
+ function eventIdentity(event) {
44
+ return [
45
+ event.serverId ?? '-',
46
+ event.sourceEventId,
47
+ JSON.stringify(stableValue(event.subscriptionArguments ?? {})),
48
+ ].join(':');
49
+ }
50
+ function eventOrder(left, right) {
51
+ return (Date.parse(left.occurredAt) - Date.parse(right.occurredAt) ||
52
+ left.sourceEventId.localeCompare(right.sourceEventId) ||
53
+ left.clauseId.localeCompare(right.clauseId));
54
+ }
55
+ function stableEvents(bindings) {
56
+ const seen = new Set();
57
+ const output = [];
58
+ for (const events of Object.values(bindings)) {
59
+ for (const event of events) {
60
+ const key = eventIdentity(event);
61
+ if (seen.has(key))
62
+ continue;
63
+ seen.add(key);
64
+ output.push(event);
65
+ }
66
+ }
67
+ return output.sort(eventOrder);
68
+ }
69
+ function indexFor(all, event) {
70
+ return all.findIndex((candidate) => candidate.clauseId === event.clauseId &&
71
+ eventIdentity(candidate) === eventIdentity(event));
72
+ }
73
+ function candidateFromBindings(bindings, all, extra = {}) {
74
+ const events = stableEvents(bindings);
75
+ const indexes = events
76
+ .map((event) => indexFor(all, event))
77
+ .filter((index) => index >= 0);
78
+ return {
79
+ bindings,
80
+ events,
81
+ startIndex: indexes.length ? Math.min(...indexes) : -1,
82
+ endIndex: indexes.length ? Math.max(...indexes) : -1,
83
+ semanticDecisions: [],
84
+ ...extra,
85
+ };
86
+ }
87
+ function emptyCandidate() {
88
+ return {
89
+ bindings: {},
90
+ events: [],
91
+ startIndex: -1,
92
+ endIndex: -1,
93
+ semanticDecisions: [],
94
+ };
95
+ }
96
+ function cloneBindings(bindings) {
97
+ return Object.fromEntries(Object.entries(bindings).map(([ref, events]) => [ref, [...events]]));
98
+ }
99
+ function mergeCandidates(left, right, all) {
100
+ const leftIds = new Set(left.events.map(eventIdentity));
101
+ if (right.events.some((event) => leftIds.has(eventIdentity(event)))) {
102
+ return null;
103
+ }
104
+ const bindings = cloneBindings(left.bindings);
105
+ for (const [ref, events] of Object.entries(right.bindings)) {
106
+ bindings[ref] = [...(bindings[ref] ?? []), ...events].sort(eventOrder);
107
+ }
108
+ const pendingUntil = [left.pendingUntil, right.pendingUntil]
109
+ .filter(Boolean)
110
+ .sort()
111
+ .at(-1);
112
+ return candidateFromBindings(bindings, all, {
113
+ ...(pendingUntil ? { pendingUntil } : {}),
114
+ semanticDecisions: [
115
+ ...left.semanticDecisions,
116
+ ...right.semanticDecisions,
117
+ ],
118
+ });
119
+ }
120
+ function candidateSignature(candidate) {
121
+ return candidate.events
122
+ .map((event) => `${event.clauseId}:${eventIdentity(event)}`)
123
+ .sort()
124
+ .join('|');
125
+ }
126
+ function dedupe(candidates, max) {
127
+ const seen = new Set();
128
+ const values = [];
129
+ let truncated = false;
130
+ for (const candidate of candidates) {
131
+ const key = [
132
+ candidateSignature(candidate),
133
+ candidate.pendingUntil ?? '-',
134
+ ].join('#');
135
+ if (seen.has(key))
136
+ continue;
137
+ seen.add(key);
138
+ if (values.length >= max) {
139
+ truncated = true;
140
+ break;
141
+ }
142
+ values.push(candidate);
143
+ }
144
+ return { values, truncated };
145
+ }
146
+ function getByPath(source, path) {
147
+ let current = source;
148
+ for (const part of path.split('.')) {
149
+ if (current === null ||
150
+ typeof current !== 'object' ||
151
+ Array.isArray(current)) {
152
+ return undefined;
153
+ }
154
+ current = current[part];
155
+ }
156
+ return current;
157
+ }
158
+ function selectedEvent(candidate, ref, select, nth) {
159
+ const events = [...(candidate.bindings[ref] ?? [])].sort(eventOrder);
160
+ if (!events.length)
161
+ return null;
162
+ if (select === 'first')
163
+ return events[0] ?? null;
164
+ if (select === 'last')
165
+ return events.at(-1) ?? null;
166
+ return events[nth ?? 0] ?? null;
167
+ }
168
+ function numeric(value) {
169
+ return typeof value === 'number' && Number.isFinite(value)
170
+ ? value
171
+ : null;
172
+ }
173
+ function evaluateValue(value, candidate) {
174
+ if (value.kind === 'literal')
175
+ return value.value;
176
+ if (value.kind === 'field') {
177
+ const event = selectedEvent(candidate, value.ref, value.select, value.nth);
178
+ return event ? getByPath(event.data, value.path) : undefined;
179
+ }
180
+ if (value.kind === 'occurredAt') {
181
+ const event = selectedEvent(candidate, value.ref, value.select, value.nth);
182
+ return event ? Date.parse(event.occurredAt) : undefined;
183
+ }
184
+ const args = value.args.map((arg) => evaluateValue(arg, candidate));
185
+ const numbers = args.map(numeric);
186
+ if (numbers.some((item) => item === null))
187
+ return undefined;
188
+ const resolved = numbers;
189
+ if (value.op === 'abs')
190
+ return Math.abs(resolved[0]);
191
+ if (value.op === 'add')
192
+ return resolved.reduce((sum, item) => sum + item, 0);
193
+ if (value.op === 'subtract') {
194
+ return resolved.slice(1).reduce((result, item) => result - item, resolved[0]);
195
+ }
196
+ if (value.op === 'multiply') {
197
+ return resolved.reduce((result, item) => result * item, 1);
198
+ }
199
+ if (resolved.slice(1).some((item) => item === 0))
200
+ return undefined;
201
+ return resolved.slice(1).reduce((result, item) => result / item, resolved[0]);
202
+ }
203
+ function compareNumeric(actual, op, expected) {
204
+ if (op === 'eq')
205
+ return actual === expected;
206
+ if (op === 'neq')
207
+ return actual !== expected;
208
+ if (op === 'gt')
209
+ return actual > expected;
210
+ if (op === 'gte')
211
+ return actual >= expected;
212
+ if (op === 'lt')
213
+ return actual < expected;
214
+ return actual <= expected;
215
+ }
216
+ function compareValue(actual, node, candidate) {
217
+ if (node.op === 'isNull')
218
+ return actual === null || actual === undefined;
219
+ if (node.op === 'type') {
220
+ const expected = node.expectedType;
221
+ if (!expected)
222
+ return false;
223
+ if (expected === 'null')
224
+ return actual === null;
225
+ if (expected === 'array')
226
+ return Array.isArray(actual);
227
+ if (expected === 'object') {
228
+ return (actual !== null &&
229
+ typeof actual === 'object' &&
230
+ !Array.isArray(actual));
231
+ }
232
+ return typeof actual === expected;
233
+ }
234
+ // Undefined means the selector/path could not be resolved. General
235
+ // comparisons must fail closed; isNull is the explicit absence operator.
236
+ if (actual === undefined)
237
+ return false;
238
+ if (node.op === 'in' || node.op === 'notIn') {
239
+ const present = (node.values ?? []).some((value) => value === actual);
240
+ return node.op === 'in' ? present : !present;
241
+ }
242
+ if (node.op === 'between') {
243
+ if (node.lower === undefined || node.upper === undefined)
244
+ return false;
245
+ if (typeof actual === 'number') {
246
+ return (typeof node.lower === 'number' &&
247
+ typeof node.upper === 'number' &&
248
+ actual >= node.lower &&
249
+ actual <= node.upper);
250
+ }
251
+ if (typeof actual === 'string') {
252
+ return (typeof node.lower === 'string' &&
253
+ typeof node.upper === 'string' &&
254
+ actual >= node.lower &&
255
+ actual <= node.upper);
256
+ }
257
+ return false;
258
+ }
259
+ const expected = node.right
260
+ ? evaluateValue(node.right, candidate)
261
+ : node.values?.[0];
262
+ if (expected === undefined)
263
+ return false;
264
+ if (node.op === 'eq')
265
+ return actual === expected;
266
+ if (node.op === 'neq')
267
+ return actual !== expected;
268
+ if (node.op === 'gt' ||
269
+ node.op === 'gte' ||
270
+ node.op === 'lt' ||
271
+ node.op === 'lte') {
272
+ return (typeof actual === 'number' &&
273
+ typeof expected === 'number' &&
274
+ compareNumeric(actual, node.op, expected));
275
+ }
276
+ if (node.op === 'contains') {
277
+ if (typeof actual === 'string')
278
+ return actual.includes(String(expected));
279
+ if (Array.isArray(actual))
280
+ return actual.includes(expected);
281
+ return false;
282
+ }
283
+ if (node.op === 'startsWith') {
284
+ return typeof actual === 'string' &&
285
+ actual.startsWith(String(expected ?? ''));
286
+ }
287
+ if (node.op === 'endsWith') {
288
+ return typeof actual === 'string' &&
289
+ actual.endsWith(String(expected ?? ''));
290
+ }
291
+ if (node.op === 'regex') {
292
+ if (typeof actual !== 'string' || typeof expected !== 'string')
293
+ return false;
294
+ try {
295
+ return new RegExp(expected, node.flags ?? '').test(actual);
296
+ }
297
+ catch {
298
+ return false;
299
+ }
300
+ }
301
+ return false;
302
+ }
303
+ function aggregateValues(candidate, ref, path) {
304
+ const events = [...(candidate.bindings[ref] ?? [])].sort(eventOrder);
305
+ return events
306
+ .map((event) => path ? getByPath(event.data, path) : event)
307
+ .filter((value) => value !== undefined);
308
+ }
309
+ export function evaluatePatternAggregate(candidate, input) {
310
+ const values = aggregateValues(candidate, input.ref, input.path);
311
+ if (input.function === 'count') {
312
+ return (candidate.bindings[input.ref] ?? []).length;
313
+ }
314
+ if (input.function === 'countDistinct') {
315
+ return new Set(values.map((value) => JSON.stringify(value))).size;
316
+ }
317
+ if (input.function === 'first')
318
+ return values[0];
319
+ if (input.function === 'last')
320
+ return values.at(-1);
321
+ if (input.function === 'nth')
322
+ return values[input.nth ?? 0];
323
+ const numbers = values
324
+ .map(numeric)
325
+ .filter((value) => value !== null);
326
+ if (!numbers.length || numbers.length !== values.length)
327
+ return undefined;
328
+ if (input.function === 'sum') {
329
+ return numbers.reduce((sum, value) => sum + value, 0);
330
+ }
331
+ if (input.function === 'avg') {
332
+ return numbers.reduce((sum, value) => sum + value, 0) / numbers.length;
333
+ }
334
+ if (input.function === 'min')
335
+ return Math.min(...numbers);
336
+ if (input.function === 'max')
337
+ return Math.max(...numbers);
338
+ if (input.function === 'stddev') {
339
+ const mean = numbers.reduce((sum, value) => sum + value, 0) / numbers.length;
340
+ const variance = numbers.reduce((sum, value) => sum + (value - mean) ** 2, 0) / numbers.length;
341
+ return Math.sqrt(variance);
342
+ }
343
+ const percentile = input.percentile ?? 0.5;
344
+ const ordered = [...numbers].sort((a, b) => a - b);
345
+ if (ordered.length === 1)
346
+ return ordered[0];
347
+ const position = (ordered.length - 1) * percentile;
348
+ const lower = Math.floor(position);
349
+ const upper = Math.ceil(position);
350
+ if (lower === upper)
351
+ return ordered[lower];
352
+ const weight = position - lower;
353
+ return ordered[lower] * (1 - weight) + ordered[upper] * weight;
354
+ }
355
+ function windowMatches(candidate, window) {
356
+ if (!candidate.events.length)
357
+ return true;
358
+ const times = candidate.events.map((event) => Date.parse(event.occurredAt));
359
+ const earliest = Math.min(...times);
360
+ const latest = Math.max(...times);
361
+ const span = latest - earliest;
362
+ if (window.type === 'within' || window.type === 'sliding') {
363
+ return span <= window.sizeMs;
364
+ }
365
+ if (window.type === 'session') {
366
+ const sorted = [...times].sort((a, b) => a - b);
367
+ return sorted.every((time, index) => index === 0 || time - sorted[index - 1] <= window.gapMs);
368
+ }
369
+ if (window.type === 'count') {
370
+ return (candidate.startIndex >= 0 &&
371
+ candidate.endIndex - candidate.startIndex + 1 <= window.size);
372
+ }
373
+ const offset = window.offsetMs;
374
+ if (window.type === 'tumbling') {
375
+ const firstBucket = Math.floor((earliest - offset) / window.sizeMs);
376
+ const lastBucket = Math.floor((latest - offset) / window.sizeMs);
377
+ return firstBucket === lastBucket;
378
+ }
379
+ const latestStart = Math.floor((latest - offset) / window.hopMs) * window.hopMs + offset;
380
+ const earliestStart = latest - window.sizeMs + 1;
381
+ for (let start = latestStart; start >= earliestStart; start -= window.hopMs) {
382
+ if (earliest >= start && latest < start + window.sizeMs)
383
+ return true;
384
+ }
385
+ return false;
386
+ }
387
+ function eventsForRef(context, ref) {
388
+ return context.events
389
+ .filter((event) => event.clauseId === ref)
390
+ .sort(eventOrder);
391
+ }
392
+ function latestBoundEvent(candidate, ref) {
393
+ return [...(candidate.bindings[ref] ?? [])].sort(eventOrder).at(-1) ?? null;
394
+ }
395
+ function withBindingEvents(candidate, ref, events, all) {
396
+ const bindings = cloneBindings(candidate.bindings);
397
+ const merged = new Map();
398
+ for (const event of [...(bindings[ref] ?? []), ...events]) {
399
+ merged.set(eventIdentity(event), event);
400
+ }
401
+ bindings[ref] = [...merged.values()].sort(eventOrder);
402
+ return candidateFromBindings(bindings, all, {
403
+ ...(candidate.pendingUntil
404
+ ? { pendingUntil: candidate.pendingUntil }
405
+ : {}),
406
+ semanticDecisions: [...candidate.semanticDecisions],
407
+ });
408
+ }
409
+ function calendarNodeMatches(node, event) {
410
+ const parts = zonedParts(event.occurredAt, node.timezone);
411
+ const minutes = parts.hour * 60 + parts.minute;
412
+ const parseClock = (value) => {
413
+ const [hour, minute] = value.split(':').map(Number);
414
+ return hour * 60 + minute;
415
+ };
416
+ if (node.weekdays && !node.weekdays.includes(parts.weekday))
417
+ return false;
418
+ if (node.dates && !node.dates.includes(parts.date))
419
+ return false;
420
+ if (node.dateRange &&
421
+ (parts.date < node.dateRange.start || parts.date > node.dateRange.end)) {
422
+ return false;
423
+ }
424
+ if (node.dayOfMonth && !node.dayOfMonth.includes(parts.day))
425
+ return false;
426
+ if (node.before && node.after) {
427
+ const before = parseClock(node.before);
428
+ const after = parseClock(node.after);
429
+ if (after <= before) {
430
+ return minutes >= after && minutes <= before;
431
+ }
432
+ return minutes >= after || minutes <= before;
433
+ }
434
+ if (node.before && minutes > parseClock(node.before))
435
+ return false;
436
+ if (node.after && minutes < parseClock(node.after))
437
+ return false;
438
+ return true;
439
+ }
440
+ function absenceNodeDueAt(node, anchor) {
441
+ if (node.forMs) {
442
+ return new Date(Date.parse(anchor.occurredAt) + node.forMs);
443
+ }
444
+ const parts = zonedParts(anchor.occurredAt, node.timezone);
445
+ const due = localDateTimeToUtc(parts, node.untilLocalTime, node.timezone);
446
+ return due.getTime() < Date.parse(anchor.occurredAt)
447
+ ? new Date(anchor.occurredAt)
448
+ : due;
449
+ }
450
+ function stateMatches(candidate, node) {
451
+ const events = [...(candidate.bindings[node.ref] ?? [])].sort(eventOrder);
452
+ if (!events.length)
453
+ return false;
454
+ const values = events.map((event) => getByPath(event.data, node.path));
455
+ if (node.op === 'stableFor') {
456
+ if (events.length < 2)
457
+ return false;
458
+ const stable = values.every((value) => value === values[0]);
459
+ const span = Date.parse(events.at(-1).occurredAt) -
460
+ Date.parse(events[0].occurredAt);
461
+ return stable && span >= (node.forMs ?? 0);
462
+ }
463
+ if (events.length < 2)
464
+ return false;
465
+ const previous = values.at(-2);
466
+ const current = values.at(-1);
467
+ if (node.op === 'changed')
468
+ return previous !== current;
469
+ if (node.op === 'changedFrom') {
470
+ return previous === node.from && current === node.to;
471
+ }
472
+ const before = numeric(previous);
473
+ const after = numeric(current);
474
+ if (before === null || after === null || node.value === undefined)
475
+ return false;
476
+ if (node.op === 'crossesAbove') {
477
+ return before <= node.value && after > node.value;
478
+ }
479
+ if (node.op === 'crossesBelow') {
480
+ return before >= node.value && after < node.value;
481
+ }
482
+ let measured;
483
+ if (node.op === 'increasedBy')
484
+ measured = after - before;
485
+ else if (node.op === 'decreasedBy')
486
+ measured = before - after;
487
+ else if (node.op === 'delta')
488
+ measured = after - before;
489
+ else {
490
+ if (before === 0)
491
+ return false;
492
+ measured = ((after - before) / Math.abs(before)) * 100;
493
+ }
494
+ return compareNumeric(measured, node.comparator, node.value);
495
+ }
496
+ function semanticSource(candidate, refs) {
497
+ const source = {};
498
+ for (const ref of refs) {
499
+ const events = [...(candidate.bindings[ref] ?? [])].sort(eventOrder);
500
+ if (events.length === 1) {
501
+ source[ref] = events[0].data;
502
+ }
503
+ else if (events.length > 1) {
504
+ source[ref] = events.at(-1).data;
505
+ source[`${ref}Events`] = events.map((event) => event.data);
506
+ }
507
+ }
508
+ return source;
509
+ }
510
+ async function withTimeout(operation, timeoutMs, label) {
511
+ let timer = null;
512
+ try {
513
+ return await Promise.race([
514
+ operation,
515
+ new Promise((_, reject) => {
516
+ timer = setTimeout(() => {
517
+ reject(new Error(`${label} timed out after ${timeoutMs}ms`));
518
+ }, timeoutMs);
519
+ }),
520
+ ]);
521
+ }
522
+ finally {
523
+ if (timer)
524
+ clearTimeout(timer);
525
+ }
526
+ }
527
+ async function evaluateNode(node, context) {
528
+ const all = context.events;
529
+ if (node.kind === 'event') {
530
+ return all
531
+ .filter((event) => event.clauseId === node.ref)
532
+ .map((event) => candidateFromBindings({ [node.ref]: [event] }, all));
533
+ }
534
+ if (node.kind === 'anyOf') {
535
+ const candidates = (await Promise.all(node.children.map((child) => evaluateNode(child, context)))).flat();
536
+ const limited = dedupe(candidates, context.maxCandidates);
537
+ context.truncated ||= limited.truncated;
538
+ return limited.values;
539
+ }
540
+ if (node.kind === 'allOf') {
541
+ let current = [emptyCandidate()];
542
+ for (const child of node.children) {
543
+ const next = await evaluateNode(child, context);
544
+ const combined = [];
545
+ for (const left of current) {
546
+ for (const right of next) {
547
+ const merged = mergeCandidates(left, right, all);
548
+ if (merged)
549
+ combined.push(merged);
550
+ }
551
+ }
552
+ const limited = dedupe(combined, context.maxCandidates);
553
+ context.truncated ||= limited.truncated;
554
+ current = limited.values;
555
+ if (!current.length)
556
+ break;
557
+ }
558
+ return current;
559
+ }
560
+ if (node.kind === 'sequence') {
561
+ let current = await evaluateNode(node.children[0], context);
562
+ for (const child of node.children.slice(1)) {
563
+ const next = await evaluateNode(child, context);
564
+ const combined = [];
565
+ for (const left of current) {
566
+ const compatible = next
567
+ .map((candidate, rank) => ({ candidate, rank }))
568
+ .filter(({ candidate: right }) => {
569
+ if (!right.events.length)
570
+ return true;
571
+ if (!left.events.length)
572
+ return true;
573
+ if (right.startIndex <= left.endIndex)
574
+ return false;
575
+ if (node.contiguity === 'next' &&
576
+ right.startIndex !== left.endIndex + 1) {
577
+ return false;
578
+ }
579
+ return true;
580
+ })
581
+ .sort((a, b) => {
582
+ const leftEmpty = a.candidate.events.length === 0;
583
+ const rightEmpty = b.candidate.events.length === 0;
584
+ if (leftEmpty !== rightEmpty) {
585
+ const preference = (child.kind === 'optional' ||
586
+ (child.kind === 'repeat' && child.min === 0))
587
+ ? child.mode
588
+ : 'lazy';
589
+ return preference === 'greedy'
590
+ ? Number(leftEmpty) - Number(rightEmpty)
591
+ : Number(rightEmpty) - Number(leftEmpty);
592
+ }
593
+ if (!leftEmpty && !rightEmpty) {
594
+ return (a.candidate.startIndex - b.candidate.startIndex ||
595
+ a.rank - b.rank);
596
+ }
597
+ return a.rank - b.rank;
598
+ })
599
+ .map(({ candidate }) => candidate);
600
+ const selected = node.contiguity === 'followedBy'
601
+ ? compatible.slice(0, 1)
602
+ : compatible;
603
+ for (const right of selected) {
604
+ const merged = mergeCandidates(left, right, all);
605
+ if (merged)
606
+ combined.push(merged);
607
+ }
608
+ }
609
+ const limited = dedupe(combined, context.maxCandidates);
610
+ context.truncated ||= limited.truncated;
611
+ current = limited.values;
612
+ if (!current.length)
613
+ break;
614
+ }
615
+ return current;
616
+ }
617
+ if (node.kind === 'repeat') {
618
+ const units = (await evaluateNode(node.child, context))
619
+ .filter((candidate) => candidate.events.length)
620
+ .sort((a, b) => a.startIndex - b.startIndex ||
621
+ a.endIndex - b.endIndex);
622
+ const max = Math.min(node.max ?? units.length, units.length);
623
+ const output = [];
624
+ if (node.min === 0) {
625
+ output.push({ candidate: emptyCandidate(), count: 0 });
626
+ }
627
+ const walk = (start, selected, count) => {
628
+ if (count >= node.min) {
629
+ output.push({ candidate: selected, count });
630
+ if (output.length >= context.maxCandidates) {
631
+ context.truncated = true;
632
+ return;
633
+ }
634
+ }
635
+ if (count >= max)
636
+ return;
637
+ const compatibleIndexes = [];
638
+ for (let index = start; index < units.length; index += 1) {
639
+ const unit = units[index];
640
+ if (selected.events.length &&
641
+ unit.startIndex <= selected.endIndex) {
642
+ continue;
643
+ }
644
+ if (selected.events.length &&
645
+ node.contiguity === 'consecutive' &&
646
+ unit.startIndex !== selected.endIndex + 1) {
647
+ break;
648
+ }
649
+ compatibleIndexes.push(index);
650
+ if (selected.events.length &&
651
+ node.contiguity === 'relaxed') {
652
+ break;
653
+ }
654
+ }
655
+ for (const index of compatibleIndexes) {
656
+ const unit = units[index];
657
+ const merged = mergeCandidates(selected, unit, all);
658
+ if (!merged)
659
+ continue;
660
+ walk(index + 1, merged, count + 1);
661
+ if (context.truncated)
662
+ return;
663
+ }
664
+ };
665
+ walk(0, emptyCandidate(), 0);
666
+ output.sort((a, b) => node.mode === 'greedy'
667
+ ? b.count - a.count || a.candidate.startIndex - b.candidate.startIndex
668
+ : a.count - b.count || a.candidate.startIndex - b.candidate.startIndex);
669
+ return dedupe(output.map((entry) => entry.candidate), context.maxCandidates).values;
670
+ }
671
+ if (node.kind === 'optional') {
672
+ const child = await evaluateNode(node.child, context);
673
+ const candidates = node.mode === 'greedy'
674
+ ? [...child, emptyCandidate()]
675
+ : [emptyCandidate(), ...child];
676
+ return dedupe(candidates, context.maxCandidates).values;
677
+ }
678
+ if (node.kind === 'window') {
679
+ return (await evaluateNode(node.child, context))
680
+ .filter((candidate) => windowMatches(candidate, node.window));
681
+ }
682
+ if (node.kind === 'calendar') {
683
+ return (await evaluateNode(node.child, context))
684
+ .filter((candidate) => {
685
+ const event = latestBoundEvent(candidate, node.ref);
686
+ return event ? calendarNodeMatches(node, event) : false;
687
+ });
688
+ }
689
+ if (node.kind === 'notPresent') {
690
+ const forbidden = eventsForRef(context, node.ref);
691
+ return (await evaluateNode(node.child, context))
692
+ .filter((candidate) => !forbidden.some((event) => {
693
+ const index = indexFor(context.events, event);
694
+ return (index >= candidate.startIndex &&
695
+ index <= candidate.endIndex);
696
+ }));
697
+ }
698
+ if (node.kind === 'after') {
699
+ return (await evaluateNode(node.child, context))
700
+ .filter((candidate) => {
701
+ const event = latestBoundEvent(candidate, node.ref);
702
+ const anchor = latestBoundEvent(candidate, node.afterRef);
703
+ return Boolean(event &&
704
+ anchor &&
705
+ Date.parse(event.occurredAt) > Date.parse(anchor.occurredAt));
706
+ });
707
+ }
708
+ if (node.kind === 'until') {
709
+ return (await evaluateNode(node.child, context))
710
+ .filter((candidate) => {
711
+ const event = latestBoundEvent(candidate, node.ref);
712
+ const boundary = latestBoundEvent(candidate, node.beforeRef);
713
+ return Boolean(event &&
714
+ boundary &&
715
+ Date.parse(event.occurredAt) <= Date.parse(boundary.occurredAt));
716
+ });
717
+ }
718
+ if (node.kind === 'threshold') {
719
+ const refEvents = eventsForRef(context, node.ref);
720
+ if (refEvents.length < node.atLeast)
721
+ return [];
722
+ return (await evaluateNode(node.child, context))
723
+ .map((candidate) => withBindingEvents(candidate, node.ref, refEvents.slice(0, node.atLeast), context.events));
724
+ }
725
+ if (node.kind === 'distinct') {
726
+ const chosen = [];
727
+ const values = new Set();
728
+ for (const event of eventsForRef(context, node.ref)) {
729
+ const value = getByPath(event.data, node.path);
730
+ if (typeof value !== 'string' &&
731
+ typeof value !== 'number' &&
732
+ typeof value !== 'boolean') {
733
+ continue;
734
+ }
735
+ const key = JSON.stringify(value);
736
+ if (values.has(key))
737
+ continue;
738
+ values.add(key);
739
+ chosen.push(event);
740
+ if (values.size >= node.atLeast)
741
+ break;
742
+ }
743
+ if (values.size < node.atLeast)
744
+ return [];
745
+ return (await evaluateNode(node.child, context))
746
+ .map((candidate) => withBindingEvents(candidate, node.ref, chosen, context.events));
747
+ }
748
+ if (node.kind === 'rate') {
749
+ const refEvents = eventsForRef(context, node.ref);
750
+ if (!refEvents.length)
751
+ return [];
752
+ const latest = Math.max(...refEvents.map((event) => Date.parse(event.occurredAt)));
753
+ const inWindow = refEvents.filter((event) => latest - Date.parse(event.occurredAt) <= node.perMs);
754
+ if (inWindow.length < node.atLeast)
755
+ return [];
756
+ return (await evaluateNode(node.child, context))
757
+ .map((candidate) => withBindingEvents(candidate, node.ref, inWindow.slice(-node.atLeast), context.events));
758
+ }
759
+ if (node.kind === 'debounce') {
760
+ const refEvents = eventsForRef(context, node.ref);
761
+ const latest = refEvents.at(-1);
762
+ if (!latest)
763
+ return [];
764
+ const dueAt = Date.parse(latest.occurredAt) + node.forMs;
765
+ const finalAt = dueAt + context.allowedLatenessMs;
766
+ return (await evaluateNode(node.child, context))
767
+ .map((candidate) => {
768
+ const augmented = withBindingEvents(candidate, node.ref, [latest], context.events);
769
+ return {
770
+ ...augmented,
771
+ ...(context.now.getTime() < finalAt
772
+ ? { pendingUntil: new Date(finalAt).toISOString() }
773
+ : {}),
774
+ };
775
+ });
776
+ }
777
+ if (node.kind === 'absence') {
778
+ const bases = await evaluateNode(node.child, context);
779
+ const forbidden = eventsForRef(context, node.ref);
780
+ const output = [];
781
+ for (const base of bases) {
782
+ const anchor = latestBoundEvent(base, node.afterRef);
783
+ if (!anchor)
784
+ continue;
785
+ const due = absenceNodeDueAt(node, anchor);
786
+ const finalAt = due.getTime() + context.allowedLatenessMs;
787
+ const anchorMs = Date.parse(anchor.occurredAt);
788
+ const blocked = forbidden.some((event) => {
789
+ const at = Date.parse(event.occurredAt);
790
+ return at >= anchorMs && at <= due.getTime();
791
+ });
792
+ if (blocked)
793
+ continue;
794
+ output.push({
795
+ ...base,
796
+ ...(context.now.getTime() < finalAt
797
+ ? { pendingUntil: new Date(finalAt).toISOString() }
798
+ : {}),
799
+ });
800
+ }
801
+ return output;
802
+ }
803
+ if (node.kind === 'notNext') {
804
+ const bases = await evaluateNode(node.child, context);
805
+ const forbidden = await evaluateNode(node.forbidden, context);
806
+ return bases.filter((base) => {
807
+ if (!base.events.length)
808
+ return false;
809
+ return !forbidden.some((candidate) => candidate.events.length &&
810
+ candidate.startIndex === base.endIndex + 1);
811
+ });
812
+ }
813
+ if (node.kind === 'notFollowedBy') {
814
+ const bases = await evaluateNode(node.child, context);
815
+ const forbidden = await evaluateNode(node.forbidden, context);
816
+ const output = [];
817
+ for (const base of bases) {
818
+ if (!base.events.length)
819
+ continue;
820
+ const anchor = Date.parse(base.events.at(-1).occurredAt);
821
+ const dueAt = anchor + node.withinMs;
822
+ const finalAt = dueAt + context.allowedLatenessMs;
823
+ const blocked = forbidden.some((candidate) => {
824
+ if (!candidate.events.length)
825
+ return false;
826
+ const startedAt = Date.parse(candidate.events[0].occurredAt);
827
+ const completedAt = Date.parse(candidate.events.at(-1).occurredAt);
828
+ return (startedAt > anchor &&
829
+ completedAt <= dueAt);
830
+ });
831
+ if (blocked)
832
+ continue;
833
+ output.push({
834
+ ...base,
835
+ ...(context.now.getTime() < finalAt
836
+ ? { pendingUntil: new Date(finalAt).toISOString() }
837
+ : {}),
838
+ });
839
+ }
840
+ return output;
841
+ }
842
+ const child = await evaluateNode(node.child, context);
843
+ if (node.kind === 'compare') {
844
+ return child.filter((candidate) => compareValue(evaluateValue(node.left, candidate), node, candidate));
845
+ }
846
+ if (node.kind === 'aggregate') {
847
+ return child.filter((candidate) => {
848
+ const actual = evaluatePatternAggregate(candidate, {
849
+ function: node.function,
850
+ ref: node.ref,
851
+ path: node.path,
852
+ nth: node.nth,
853
+ percentile: node.percentile,
854
+ });
855
+ return (typeof actual === 'number' &&
856
+ compareNumeric(actual, node.op, node.value));
857
+ });
858
+ }
859
+ if (node.kind === 'state') {
860
+ return child.filter((candidate) => stateMatches(candidate, node));
861
+ }
862
+ const output = [];
863
+ for (const candidate of child) {
864
+ if (!context.evaluator) {
865
+ if (node.execution.onUnavailable === 'reject')
866
+ continue;
867
+ throw new Error(`Semantic node ${node.id} requires a SemanticEvaluator`);
868
+ }
869
+ if (context.semanticEvaluations >= context.maxSemanticEvaluations) {
870
+ context.truncated = true;
871
+ break;
872
+ }
873
+ const localCacheKey = [
874
+ node.id,
875
+ candidateSignature(candidate),
876
+ node.instruction,
877
+ JSON.stringify(node.input),
878
+ node.matchThreshold,
879
+ node.rejectThreshold,
880
+ node.uncertain,
881
+ ].join(':');
882
+ const durableCacheKey = node.execution.cache
883
+ ? durableSemanticCacheKey(context, node, candidate)
884
+ : null;
885
+ let decision = context.semanticCache.get(localCacheKey);
886
+ if (decision) {
887
+ context.semanticCacheHits += 1;
888
+ }
889
+ if (!decision &&
890
+ durableCacheKey &&
891
+ context.durableSemanticCache) {
892
+ decision =
893
+ await context.durableSemanticCache.get(durableCacheKey) ??
894
+ undefined;
895
+ if (decision) {
896
+ context.semanticCacheHits += 1;
897
+ context.semanticCache.set(localCacheKey, decision);
898
+ }
899
+ }
900
+ if (!decision) {
901
+ context.semanticCacheMisses += 1;
902
+ context.semanticEvaluations += 1;
903
+ decision = await withTimeout(new SemanticConditionEngine(context.evaluator).evaluate(semanticSource(candidate, node.refs), {
904
+ type: 'semantic_boolean',
905
+ instruction: node.instruction,
906
+ input: node.input,
907
+ matchThreshold: node.matchThreshold,
908
+ rejectThreshold: node.rejectThreshold,
909
+ uncertain: node.uncertain,
910
+ }), node.execution.timeoutMs, `Semantic node ${node.id}`);
911
+ if (node.execution.cache) {
912
+ context.semanticCache.set(localCacheKey, decision);
913
+ if (durableCacheKey &&
914
+ context.durableSemanticCache) {
915
+ await context.durableSemanticCache.set(durableCacheKey, decision);
916
+ }
917
+ }
918
+ }
919
+ context.semanticTrace.push({
920
+ nodeId: node.id,
921
+ candidateSignature: candidateSignature(candidate),
922
+ decision,
923
+ });
924
+ if (!decision.matched)
925
+ continue;
926
+ output.push({
927
+ ...candidate,
928
+ semanticDecisions: [
929
+ ...candidate.semanticDecisions,
930
+ { nodeId: node.id, decision },
931
+ ],
932
+ });
933
+ }
934
+ return output;
935
+ }
936
+ export function evaluatePatternMeasure(candidate, measure) {
937
+ const expression = measure.expression;
938
+ if (expression.kind === 'aggregate') {
939
+ return evaluatePatternAggregate(candidate, {
940
+ function: expression.function,
941
+ ref: expression.ref,
942
+ path: expression.path,
943
+ nth: expression.nth,
944
+ percentile: expression.percentile,
945
+ });
946
+ }
947
+ return evaluateValue(expression, candidate);
948
+ }
949
+ export async function evaluatePatternV2({ definition, events, evaluator = null, now = new Date(), allowedLatenessMs = 0, semanticCache = null, semanticCacheNamespace = '', }) {
950
+ const ordered = [...events].sort(eventOrder);
951
+ const context = {
952
+ events: ordered,
953
+ evaluator,
954
+ now,
955
+ maxCandidates: definition.execution.maxCandidates,
956
+ maxSemanticEvaluations: definition.execution.maxSemanticEvaluations,
957
+ semanticEvaluations: 0,
958
+ semanticCacheHits: 0,
959
+ semanticCacheMisses: 0,
960
+ truncated: false,
961
+ semanticCache: new Map(),
962
+ durableSemanticCache: semanticCache,
963
+ semanticCacheNamespace,
964
+ semanticTrace: [],
965
+ allowedLatenessMs,
966
+ };
967
+ const candidates = await evaluateNode(definition.root, context);
968
+ const limited = dedupe(candidates, context.maxCandidates);
969
+ context.truncated ||= limited.truncated;
970
+ const matches = limited.values.filter((candidate) => !candidate.pendingUntil ||
971
+ Date.parse(candidate.pendingUntil) <= now.getTime());
972
+ const pending = limited.values.filter((candidate) => candidate.pendingUntil &&
973
+ Date.parse(candidate.pendingUntil) > now.getTime());
974
+ return {
975
+ matches,
976
+ pending,
977
+ semanticEvaluations: context.semanticEvaluations,
978
+ semanticCacheHits: context.semanticCacheHits,
979
+ semanticCacheMisses: context.semanticCacheMisses,
980
+ semanticTrace: context.semanticTrace,
981
+ truncated: context.truncated,
982
+ };
983
+ }
984
+ export function patternV2CandidateSignature(candidate) {
985
+ return candidateSignature(candidate);
986
+ }