mcp-event-intelligence 0.1.0 → 0.2.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 (32) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +110 -5
  3. package/bin/mcp-event-intelligence.mjs +5 -1
  4. package/dist/src/composite/derivedGraph.d.ts +4 -0
  5. package/dist/src/composite/derivedGraph.js +46 -0
  6. package/dist/src/composite/engine.d.ts +3 -64
  7. package/dist/src/composite/engine.js +26 -343
  8. package/dist/src/composite/matching.d.ts +8 -0
  9. package/dist/src/composite/matching.js +109 -0
  10. package/dist/src/composite/runtimeSupport.d.ts +24 -0
  11. package/dist/src/composite/runtimeSupport.js +176 -0
  12. package/dist/src/composite/store.d.ts +59 -0
  13. package/dist/src/composite/store.js +1 -0
  14. package/dist/src/intelligenceProtocol/schemas.d.ts +9 -7
  15. package/dist/src/intelligenceProtocol/schemas.js +1 -0
  16. package/dist/src/intelligenceProtocol/triggerSchemas.d.ts +6 -6
  17. package/dist/src/mcpEvents/erpnextProvider.d.ts +20 -0
  18. package/dist/src/mcpEvents/erpnextProvider.js +80 -0
  19. package/dist/src/mcpEvents/provider.d.ts +48 -0
  20. package/dist/src/mcpEvents/provider.js +296 -0
  21. package/package.json +9 -1
  22. package/scripts/host-integration.d.mts +34 -4
  23. package/scripts/host-integration.mjs +44 -1
  24. package/scripts/lib/composite-wake-coordinator.mjs +233 -9
  25. package/scripts/lib/event-processor.mjs +235 -20
  26. package/scripts/lib/local-event-intelligence-runtime.mjs +227 -82
  27. package/scripts/lib/mcp-events-client.mjs +173 -66
  28. package/scripts/lib/persistent-event-store.mjs +308 -1
  29. package/scripts/lib/wake-retry-scheduler.mjs +94 -0
  30. package/scripts/mcp-stdio-server.mjs +1 -1
  31. package/scripts/service.mjs +20 -0
  32. package/server.json +2 -2
@@ -2,169 +2,19 @@ import { SemanticConditionEngine } from '../semantic/conditionEngine.js';
2
2
  import { sha256Hex } from '../intelligenceProtocol/canonical.js';
3
3
  import { COMPOSITE_TRIGGER_PROTOCOL_VERSION, COMPOSITE_TRIGGER_SCHEMA_VERSION, CompositeTriggerDefinitionSchema, CorrelatableEventSchema, TriggerMatchRecordSchema, } from '../intelligenceProtocol/triggerSchemas.js';
4
4
  import { evaluateTemporalConditions, } from './temporal.js';
5
- function getByPath(source, path) {
6
- let current = source;
7
- for (const part of path.split('.')) {
8
- if (current === null ||
9
- typeof current !== 'object' ||
10
- Array.isArray(current))
11
- return undefined;
12
- current = current[part];
13
- }
14
- return current;
15
- }
16
- function scalarKey(value) {
17
- if (typeof value === 'string' ||
18
- typeof value === 'number' ||
19
- typeof value === 'boolean')
20
- return String(value);
21
- return null;
22
- }
23
- function predicateMatches(event, predicate) {
24
- const value = getByPath(event.data, predicate.path);
25
- switch (predicate.op) {
26
- case 'eq':
27
- return value === predicate.value;
28
- case 'neq':
29
- return value !== predicate.value;
30
- case 'contains':
31
- if (typeof value === 'string')
32
- return value.includes(String(predicate.value));
33
- if (Array.isArray(value))
34
- return value.includes(predicate.value);
35
- return false;
36
- case 'in':
37
- return predicate.value.includes(value);
38
- case 'exists':
39
- return predicate.value ? value !== undefined : value === undefined;
40
- }
41
- }
42
- function clauseMatches(clause, event) {
43
- const contractVersion = getByPath(event.data, '_derived.contractVersion');
44
- return (clause.event === event.name &&
45
- (!clause.serverId || clause.serverId === event.serverId) &&
46
- (!clause.contractVersion ||
47
- String(contractVersion ?? '') === String(clause.contractVersion)) &&
48
- clause.where.every((predicate) => predicateMatches(event, predicate)));
49
- }
50
- function asSourceEvent(clauseId, event) {
51
- return {
52
- clauseId,
53
- traceId: event.traceId,
54
- sourceEventId: event.sourceEventId,
55
- eventName: event.name,
56
- occurredAt: event.occurredAt,
57
- ...(event.provider ? { provider: event.provider } : {}),
58
- ...(event.serverId ? { serverId: event.serverId } : {}),
59
- ...(event.payloadHash ? { payloadHash: event.payloadHash } : {}),
60
- data: event.data,
61
- };
62
- }
63
- function clauseEvents(record, ref) {
64
- return record.sourceEvents
65
- .filter((event) => event.clauseId === ref)
66
- .sort((a, b) => Date.parse(a.occurredAt) - Date.parse(b.occurredAt));
67
- }
68
- function expressionSatisfied(definition, record) {
69
- const expression = definition.expression;
70
- if (expression.kind === 'allOf') {
71
- return expression.refs.every((ref) => clauseEvents(record, ref).length > 0);
72
- }
73
- if (expression.kind === 'anyOf') {
74
- return expression.refs.some((ref) => clauseEvents(record, ref).length > 0);
75
- }
76
- if (expression.kind === 'count') {
77
- return clauseEvents(record, expression.ref).length >= expression.atLeast;
78
- }
79
- let previous = Number.NEGATIVE_INFINITY;
80
- for (const ref of expression.refs) {
81
- const candidate = clauseEvents(record, ref)
82
- .find((event) => Date.parse(event.occurredAt) > previous);
83
- if (!candidate)
84
- return false;
85
- previous = Date.parse(candidate.occurredAt);
86
- }
87
- return true;
88
- }
89
- function semanticSource(record) {
90
- const source = {};
91
- for (const event of record.sourceEvents) {
92
- source[event.clauseId] = event.data;
93
- }
94
- return source;
95
- }
96
- function deterministicKeyForClause(definition, clauseId, event) {
97
- const deterministic = definition.correlation?.deterministic;
98
- if (!deterministic)
99
- return null;
100
- const field = deterministic.fields.find((item) => item.ref === clauseId);
101
- if (!field)
102
- return null;
103
- return scalarKey(getByPath(event.data, field.path));
104
- }
105
- function sameEventIdentity(source, event) {
106
- return (source.sourceEventId === event.sourceEventId &&
107
- (source.serverId ?? null) === (event.serverId ?? null));
108
- }
109
- function matchesCorrelationKey(record, key) {
110
- if (record.correlationKey === null || key === null)
111
- return true;
112
- return record.correlationKey === key;
113
- }
114
- const DERIVED_SERVER_ID = 'event-intelligence:derived';
115
- function eventNode(eventName, serverId) {
116
- return `${serverId || DERIVED_SERVER_ID}::${eventName}`;
117
- }
118
- function assertNoDerivedEventCycle(existing, candidate, getState) {
119
- const definitions = [
120
- ...existing.filter((definition) => {
121
- const state = getState?.(definition.triggerId, definition.version);
122
- return !state || state.status === 'active' || state.status === 'paused';
123
- }),
124
- candidate,
125
- ].filter((definition) => definition.derivedEvent);
126
- const graph = new Map();
127
- for (const definition of definitions) {
128
- const output = eventNode(definition.derivedEvent.name, DERIVED_SERVER_ID);
129
- for (const clause of definition.clauses) {
130
- const input = eventNode(clause.event, clause.serverId);
131
- const edges = graph.get(input) ?? new Set();
132
- edges.add(output);
133
- graph.set(input, edges);
134
- }
135
- }
136
- const visiting = new Set();
137
- const visited = new Set();
138
- const dfs = (node) => {
139
- if (visiting.has(node))
140
- return true;
141
- if (visited.has(node))
142
- return false;
143
- visiting.add(node);
144
- for (const next of graph.get(node) ?? []) {
145
- if (dfs(next))
146
- return true;
147
- }
148
- visiting.delete(node);
149
- visited.add(node);
150
- return false;
151
- };
152
- for (const node of graph.keys()) {
153
- if (dfs(node)) {
154
- const error = new Error(`Derived event graph contains a cycle involving ${node}`);
155
- error.code = 'TRIGGER_DERIVED_EVENT_CYCLE';
156
- throw error;
157
- }
158
- }
159
- }
5
+ import { asSourceEvent, clauseMatches, deterministicKeyForClause, expressionSatisfied, matchesCorrelationKey, sameEventIdentity, semanticSource, } from './matching.js';
6
+ import { assertNoDerivedEventCycle } from './derivedGraph.js';
7
+ import { CompositeTriggerRuntimeSupport, } from './runtimeSupport.js';
160
8
  export class CompositeTriggerEngine {
161
9
  store;
162
10
  evaluator;
163
11
  now;
12
+ support;
164
13
  constructor(store, evaluator = null, now = () => new Date()) {
165
14
  this.store = store;
166
15
  this.evaluator = evaluator;
167
16
  this.now = now;
17
+ this.support = new CompositeTriggerRuntimeSupport(store, now);
168
18
  }
169
19
  async register(definitionInput) {
170
20
  const definition = CompositeTriggerDefinitionSchema.parse(definitionInput);
@@ -180,7 +30,7 @@ export class CompositeTriggerEngine {
180
30
  const results = [];
181
31
  for (const definition of this.store.listTriggers()) {
182
32
  const triggerState = this.store.getTriggerState?.(definition.triggerId, definition.version) ?? null;
183
- if (!(await this.lifecycleAllows(definition, triggerState, this.now()))) {
33
+ if (!(await this.support.lifecycleAllows(definition, triggerState, this.now()))) {
184
34
  continue;
185
35
  }
186
36
  const matchingClauses = definition.clauses.filter((clause) => clauseMatches(clause, event));
@@ -221,9 +71,9 @@ export class CompositeTriggerEngine {
221
71
  updatedAt: this.now().toISOString(),
222
72
  });
223
73
  await this.store.appendTriggerMatch(fired);
224
- await this.cancelDeadlinesForMatch(matchId);
225
- await this.audit(fired, 'trigger.fired', { wakeId });
226
- await this.advanceLifecycleAfterEffect(fired, 'wake', wakeId);
74
+ await this.support.cancelDeadlinesForMatch(matchId);
75
+ await this.support.auditMatch(fired, 'trigger.fired', { wakeId });
76
+ await this.support.advanceLifecycleAfterEffect(fired, 'wake', wakeId);
227
77
  return fired;
228
78
  }
229
79
  async markDerivedEmitted(matchId, derivedEventId) {
@@ -246,11 +96,11 @@ export class CompositeTriggerEngine {
246
96
  ],
247
97
  });
248
98
  await this.store.appendTriggerMatch(emitted);
249
- await this.cancelDeadlinesForMatch(matchId);
250
- await this.audit(emitted, 'trigger.emitted', {
99
+ await this.support.cancelDeadlinesForMatch(matchId);
100
+ await this.support.auditMatch(emitted, 'trigger.emitted', {
251
101
  derivedEventId,
252
102
  });
253
- await this.advanceLifecycleAfterEffect(emitted, 'derived_event', derivedEventId);
103
+ await this.support.advanceLifecycleAfterEffect(emitted, 'derived_event', derivedEventId);
254
104
  return emitted;
255
105
  }
256
106
  async ingestTimerEvent(event) {
@@ -265,7 +115,7 @@ export class CompositeTriggerEngine {
265
115
  if (!definition)
266
116
  return [];
267
117
  const triggerState = this.store.getTriggerState?.(definition.triggerId, definition.version) ?? null;
268
- if (!(await this.lifecycleAllows(definition, triggerState, this.now()))) {
118
+ if (!(await this.support.lifecycleAllows(definition, triggerState, this.now()))) {
269
119
  return [];
270
120
  }
271
121
  const match = this.latestMatch(deadline.matchId);
@@ -283,8 +133,8 @@ export class CompositeTriggerEngine {
283
133
  updatedAt: event.occurredAt,
284
134
  });
285
135
  await this.store.appendTriggerMatch(expired);
286
- await this.cancelDeadlinesForMatch(expired.matchId);
287
- await this.audit(expired, 'trigger.expired', {
136
+ await this.support.cancelDeadlinesForMatch(expired.matchId);
137
+ await this.support.auditMatch(expired, 'trigger.expired', {
288
138
  reason: 'temporal_deadline_after_event_window',
289
139
  });
290
140
  return [{
@@ -323,8 +173,8 @@ export class CompositeTriggerEngine {
323
173
  updatedAt: nowIso,
324
174
  });
325
175
  await this.store.appendTriggerMatch(expired);
326
- await this.cancelDeadlinesForMatch(expired.matchId);
327
- await this.audit(expired, 'trigger.expired', {
176
+ await this.support.cancelDeadlinesForMatch(expired.matchId);
177
+ await this.support.auditMatch(expired, 'trigger.expired', {
328
178
  reason: 'window_expired_before_event',
329
179
  });
330
180
  record = await this.newMatch(definition, key, event);
@@ -343,7 +193,7 @@ export class CompositeTriggerEngine {
343
193
  }
344
194
  if (!expressionSatisfied(definition, record)) {
345
195
  await this.store.appendTriggerMatch(record);
346
- await this.audit(record, 'trigger.partial', {
196
+ await this.support.auditMatch(record, 'trigger.partial', {
347
197
  clauseId: clause.id,
348
198
  sourceEventId: event.sourceEventId,
349
199
  });
@@ -366,8 +216,8 @@ export class CompositeTriggerEngine {
366
216
  updatedAt: nowIso,
367
217
  });
368
218
  await this.store.appendTriggerMatch(expired);
369
- await this.cancelDeadlinesForMatch(expired.matchId);
370
- await this.audit(expired, 'trigger.expired', {
219
+ await this.support.cancelDeadlinesForMatch(expired.matchId);
220
+ await this.support.auditMatch(expired, 'trigger.expired', {
371
221
  reason: 'temporal_condition_blocked',
372
222
  temporal: temporal.conditionStates,
373
223
  });
@@ -384,8 +234,8 @@ export class CompositeTriggerEngine {
384
234
  status: 'partial',
385
235
  updatedAt: nowIso,
386
236
  }));
387
- await this.scheduleDeadlines(definition, record, temporal.pendingDeadlines);
388
- await this.audit(record, 'trigger.partial', {
237
+ await this.support.scheduleDeadlines(definition, record, temporal.pendingDeadlines);
238
+ await this.support.auditMatch(record, 'trigger.partial', {
389
239
  reason: 'temporal_wait',
390
240
  temporal: temporal.conditionStates,
391
241
  });
@@ -396,11 +246,11 @@ export class CompositeTriggerEngine {
396
246
  fired: false,
397
247
  };
398
248
  }
399
- await this.cancelDeadlinesForMatch(record.matchId);
249
+ await this.support.cancelDeadlinesForMatch(record.matchId);
400
250
  if (definition.correlation?.semantic) {
401
251
  if (!this.evaluator) {
402
252
  await this.store.appendTriggerMatch(record);
403
- await this.audit(record, 'trigger.partial', {
253
+ await this.support.auditMatch(record, 'trigger.partial', {
404
254
  reason: 'semantic_evaluator_unavailable',
405
255
  });
406
256
  return {
@@ -434,7 +284,7 @@ export class CompositeTriggerEngine {
434
284
  updatedAt: nowIso,
435
285
  });
436
286
  await this.store.appendTriggerMatch(record);
437
- await this.audit(record, 'trigger.correlation', {
287
+ await this.support.auditMatch(record, 'trigger.correlation', {
438
288
  outcome: decision.outcome,
439
289
  probability: decision.probability,
440
290
  evaluator: decision.evaluator,
@@ -454,7 +304,7 @@ export class CompositeTriggerEngine {
454
304
  updatedAt: nowIso,
455
305
  });
456
306
  await this.store.appendTriggerMatch(matched);
457
- await this.audit(matched, 'trigger.matched', {
307
+ await this.support.auditMatch(matched, 'trigger.matched', {
458
308
  sourceEventIds: matched.sourceEvents.map((item) => item.sourceEventId),
459
309
  temporal: temporal.conditionStates,
460
310
  });
@@ -465,156 +315,6 @@ export class CompositeTriggerEngine {
465
315
  fired: false,
466
316
  };
467
317
  }
468
- async scheduleDeadlines(definition, record, deadlines) {
469
- if (!this.store.putTemporalDeadline)
470
- return;
471
- for (const deadline of deadlines) {
472
- const deadlineId = `td_${(await sha256Hex([
473
- definition.triggerId,
474
- definition.version,
475
- record.matchId,
476
- deadline.conditionId,
477
- deadline.dueAt,
478
- ].join(':'))).slice(0, 24)}`;
479
- const existing = this.store.getTemporalDeadline?.(deadlineId);
480
- if (existing?.status === 'pending' || existing?.status === 'fired') {
481
- continue;
482
- }
483
- await this.store.putTemporalDeadline({
484
- deadlineId,
485
- triggerId: definition.triggerId,
486
- triggerVersion: definition.version,
487
- matchId: record.matchId,
488
- conditionId: deadline.conditionId,
489
- dueAt: deadline.dueAt,
490
- status: 'pending',
491
- });
492
- }
493
- }
494
- async cancelDeadlinesForMatch(matchId) {
495
- if (!this.store.listTemporalDeadlines ||
496
- !this.store.setTemporalDeadlineStatus)
497
- return;
498
- const pending = this.store.listTemporalDeadlines({
499
- matchId,
500
- status: 'pending',
501
- });
502
- for (const deadline of pending) {
503
- await this.store.setTemporalDeadlineStatus(deadline.deadlineId, 'cancelled');
504
- }
505
- }
506
- async advanceLifecycleAfterEffect(match, effectKind, effectId) {
507
- const definition = this.store.listTriggers().find((candidate) => candidate.triggerId === match.triggerId &&
508
- candidate.version === match.triggerVersion);
509
- const triggerState = this.store.getTriggerState?.(match.triggerId, match.triggerVersion) ?? null;
510
- if (!definition || !this.store.setTriggerState)
511
- return;
512
- const previousCount = triggerState?.fireCount ?? 0;
513
- const fireCount = previousCount + 1;
514
- const lifecycle = definition.lifecycle ?? {};
515
- const complete = lifecycle.oneShot === true ||
516
- lifecycle.completeOnGoal === true ||
517
- (lifecycle.maxFirings !== undefined &&
518
- fireCount >= lifecycle.maxFirings);
519
- const nextStatus = complete ? 'completed' : 'active';
520
- const effectAt = this.now().toISOString();
521
- await this.store.setTriggerState(match.triggerId, match.triggerVersion, nextStatus, {
522
- type: 'system',
523
- principal_id: 'event-intelligence:lifecycle',
524
- }, {
525
- owner: triggerState?.owner ?? null,
526
- connectionIds: triggerState?.connectionIds ?? [],
527
- fireCount,
528
- lastFiredAt: effectAt,
529
- });
530
- if (complete) {
531
- await this.auditTriggerLifecycle(definition, 'trigger.completed', {
532
- reason: lifecycle.oneShot
533
- ? 'one_shot'
534
- : lifecycle.completeOnGoal
535
- ? 'complete_on_goal'
536
- : 'max_firings',
537
- fireCount,
538
- effectKind,
539
- effectId,
540
- });
541
- }
542
- }
543
- async lifecycleAllows(definition, state, now) {
544
- const lifecycle = definition.lifecycle ?? {};
545
- const effectiveState = state ?? {
546
- status: 'active',
547
- fireCount: 0,
548
- lastFiredAt: null,
549
- connectionIds: [],
550
- owner: null,
551
- };
552
- if (effectiveState.status !== 'active')
553
- return false;
554
- const expiresAt = lifecycle.expiresAt
555
- ? Date.parse(lifecycle.expiresAt)
556
- : Number.POSITIVE_INFINITY;
557
- const leaseUntil = lifecycle.leaseUntil
558
- ? Date.parse(lifecycle.leaseUntil)
559
- : Number.POSITIVE_INFINITY;
560
- if (now.getTime() > Math.min(expiresAt, leaseUntil)) {
561
- if (this.store.setTriggerState) {
562
- await this.store.setTriggerState(definition.triggerId, definition.version, 'expired', {
563
- type: 'system',
564
- principal_id: 'event-intelligence:lifecycle',
565
- }, {
566
- owner: effectiveState.owner ?? null,
567
- connectionIds: effectiveState.connectionIds ?? [],
568
- fireCount: effectiveState.fireCount ?? 0,
569
- lastFiredAt: effectiveState.lastFiredAt ?? null,
570
- });
571
- }
572
- await this.auditTriggerLifecycle(definition, 'trigger.lifecycle_expired', {
573
- expiresAt: lifecycle.expiresAt ?? null,
574
- leaseUntil: lifecycle.leaseUntil ?? null,
575
- });
576
- return false;
577
- }
578
- const fireCount = effectiveState.fireCount ?? 0;
579
- if ((lifecycle.oneShot && fireCount >= 1) ||
580
- (lifecycle.maxFirings !== undefined &&
581
- fireCount >= lifecycle.maxFirings)) {
582
- if (this.store.setTriggerState) {
583
- await this.store.setTriggerState(definition.triggerId, definition.version, 'completed', {
584
- type: 'system',
585
- principal_id: 'event-intelligence:lifecycle',
586
- }, {
587
- owner: effectiveState.owner ?? null,
588
- connectionIds: effectiveState.connectionIds ?? [],
589
- fireCount,
590
- lastFiredAt: effectiveState.lastFiredAt ?? null,
591
- });
592
- }
593
- return false;
594
- }
595
- if (lifecycle.cooldownMs > 0 &&
596
- effectiveState.lastFiredAt &&
597
- now.getTime() - Date.parse(effectiveState.lastFiredAt) <
598
- lifecycle.cooldownMs) {
599
- return false;
600
- }
601
- return true;
602
- }
603
- async auditTriggerLifecycle(definition, kind, details) {
604
- const timestamp = this.now().toISOString();
605
- await this.store.appendAudit({
606
- auditId: `audit_${(await sha256Hex(`${definition.triggerId}:${definition.version}:${kind}:${timestamp}`)).slice(0, 24)}`,
607
- traceId: `trigger:${definition.triggerId}`,
608
- timestamp,
609
- kind,
610
- entityType: 'trigger',
611
- entityId: definition.triggerId,
612
- details: {
613
- triggerVersion: definition.version,
614
- ...details,
615
- },
616
- });
617
- }
618
318
  async newMatch(definition, key, event) {
619
319
  const openedAt = event.occurredAt;
620
320
  const expiresAt = new Date(Date.parse(openedAt) + definition.withinMs).toISOString();
@@ -647,21 +347,4 @@ export class CompositeTriggerEngine {
647
347
  .filter((record) => record.matchId === matchId)
648
348
  .sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))[0] ?? null;
649
349
  }
650
- async audit(record, kind, details = {}) {
651
- await this.store.appendAudit({
652
- auditId: `audit_${(await sha256Hex(`${record.matchId}:${kind}:${record.updatedAt}:${this.store.listTriggerMatches().length}`)).slice(0, 24)}`,
653
- traceId: record.sourceEvents[0]?.traceId ?? record.matchId,
654
- timestamp: this.now().toISOString(),
655
- kind,
656
- entityType: 'trigger_match',
657
- entityId: record.matchId,
658
- details: {
659
- triggerId: record.triggerId,
660
- triggerVersion: record.triggerVersion,
661
- status: record.status,
662
- correlationKey: record.correlationKey,
663
- ...details,
664
- },
665
- });
666
- }
667
350
  }
@@ -0,0 +1,8 @@
1
+ import type { CompositeTriggerDefinition, CorrelatableEvent, TriggerClause, TriggerMatchRecord, TriggerSourceEvent } from '../intelligenceProtocol/triggerSchemas.js';
2
+ export declare function clauseMatches(clause: TriggerClause, event: CorrelatableEvent): boolean;
3
+ export declare function asSourceEvent(clauseId: string, event: CorrelatableEvent): TriggerSourceEvent;
4
+ export declare function expressionSatisfied(definition: CompositeTriggerDefinition, record: TriggerMatchRecord): boolean;
5
+ export declare function semanticSource(record: TriggerMatchRecord): Record<string, unknown>;
6
+ export declare function deterministicKeyForClause(definition: CompositeTriggerDefinition, clauseId: string, event: CorrelatableEvent): string | null;
7
+ export declare function sameEventIdentity(source: Pick<TriggerSourceEvent, 'sourceEventId' | 'serverId'>, event: Pick<CorrelatableEvent, 'sourceEventId' | 'serverId'>): boolean;
8
+ export declare function matchesCorrelationKey(record: TriggerMatchRecord, key: string | null): boolean;
@@ -0,0 +1,109 @@
1
+ function getByPath(source, path) {
2
+ let current = source;
3
+ for (const part of path.split('.')) {
4
+ if (current === null ||
5
+ typeof current !== 'object' ||
6
+ Array.isArray(current))
7
+ return undefined;
8
+ current = current[part];
9
+ }
10
+ return current;
11
+ }
12
+ function scalarKey(value) {
13
+ if (typeof value === 'string' ||
14
+ typeof value === 'number' ||
15
+ typeof value === 'boolean')
16
+ return String(value);
17
+ return null;
18
+ }
19
+ function predicateMatches(event, predicate) {
20
+ const value = getByPath(event.data, predicate.path);
21
+ switch (predicate.op) {
22
+ case 'eq':
23
+ return value === predicate.value;
24
+ case 'neq':
25
+ return value !== predicate.value;
26
+ case 'contains':
27
+ if (typeof value === 'string')
28
+ return value.includes(String(predicate.value));
29
+ if (Array.isArray(value))
30
+ return value.includes(predicate.value);
31
+ return false;
32
+ case 'in':
33
+ return predicate.value.includes(value);
34
+ case 'exists':
35
+ return predicate.value ? value !== undefined : value === undefined;
36
+ }
37
+ }
38
+ export function clauseMatches(clause, event) {
39
+ const contractVersion = getByPath(event.data, '_derived.contractVersion');
40
+ return (clause.event === event.name &&
41
+ (!clause.serverId || clause.serverId === event.serverId) &&
42
+ (!clause.contractVersion ||
43
+ String(contractVersion ?? '') === String(clause.contractVersion)) &&
44
+ clause.where.every((predicate) => predicateMatches(event, predicate)));
45
+ }
46
+ export function asSourceEvent(clauseId, event) {
47
+ return {
48
+ clauseId,
49
+ traceId: event.traceId,
50
+ sourceEventId: event.sourceEventId,
51
+ eventName: event.name,
52
+ occurredAt: event.occurredAt,
53
+ ...(event.provider ? { provider: event.provider } : {}),
54
+ ...(event.serverId ? { serverId: event.serverId } : {}),
55
+ ...(event.payloadHash ? { payloadHash: event.payloadHash } : {}),
56
+ data: event.data,
57
+ };
58
+ }
59
+ function clauseEvents(record, ref) {
60
+ return record.sourceEvents
61
+ .filter((event) => event.clauseId === ref)
62
+ .sort((a, b) => Date.parse(a.occurredAt) - Date.parse(b.occurredAt));
63
+ }
64
+ export function expressionSatisfied(definition, record) {
65
+ const expression = definition.expression;
66
+ if (expression.kind === 'allOf') {
67
+ return expression.refs.every((ref) => clauseEvents(record, ref).length > 0);
68
+ }
69
+ if (expression.kind === 'anyOf') {
70
+ return expression.refs.some((ref) => clauseEvents(record, ref).length > 0);
71
+ }
72
+ if (expression.kind === 'count') {
73
+ return clauseEvents(record, expression.ref).length >= expression.atLeast;
74
+ }
75
+ let previous = Number.NEGATIVE_INFINITY;
76
+ for (const ref of expression.refs) {
77
+ const candidate = clauseEvents(record, ref)
78
+ .find((event) => Date.parse(event.occurredAt) > previous);
79
+ if (!candidate)
80
+ return false;
81
+ previous = Date.parse(candidate.occurredAt);
82
+ }
83
+ return true;
84
+ }
85
+ export function semanticSource(record) {
86
+ const source = {};
87
+ for (const event of record.sourceEvents) {
88
+ source[event.clauseId] = event.data;
89
+ }
90
+ return source;
91
+ }
92
+ export function deterministicKeyForClause(definition, clauseId, event) {
93
+ const deterministic = definition.correlation?.deterministic;
94
+ if (!deterministic)
95
+ return null;
96
+ const field = deterministic.fields.find((item) => item.ref === clauseId);
97
+ if (!field)
98
+ return null;
99
+ return scalarKey(getByPath(event.data, field.path));
100
+ }
101
+ export function sameEventIdentity(source, event) {
102
+ return (source.sourceEventId === event.sourceEventId &&
103
+ (source.serverId ?? null) === (event.serverId ?? null));
104
+ }
105
+ export function matchesCorrelationKey(record, key) {
106
+ if (record.correlationKey === null || key === null)
107
+ return true;
108
+ return record.correlationKey === key;
109
+ }
@@ -0,0 +1,24 @@
1
+ import type { CompositeTriggerDefinition, TriggerMatchRecord } from '../intelligenceProtocol/triggerSchemas.js';
2
+ import type { CompositeTriggerAudit, CompositeTriggerStore } from './store.js';
3
+ type TriggerState = {
4
+ status: 'active' | 'paused' | 'completed' | 'expired' | 'deleted';
5
+ owner?: unknown;
6
+ connectionIds?: string[];
7
+ fireCount?: number;
8
+ lastFiredAt?: string | null;
9
+ };
10
+ export declare class CompositeTriggerRuntimeSupport {
11
+ private readonly store;
12
+ private readonly now;
13
+ constructor(store: CompositeTriggerStore & CompositeTriggerAudit, now?: () => Date);
14
+ scheduleDeadlines(definition: CompositeTriggerDefinition, record: TriggerMatchRecord, deadlines: Array<{
15
+ conditionId: string;
16
+ dueAt: string;
17
+ }>): Promise<void>;
18
+ cancelDeadlinesForMatch(matchId: string): Promise<void>;
19
+ advanceLifecycleAfterEffect(match: TriggerMatchRecord, effectKind: 'wake' | 'derived_event', effectId: string): Promise<void>;
20
+ lifecycleAllows(definition: CompositeTriggerDefinition, state: TriggerState | null, now: Date): Promise<boolean>;
21
+ auditTriggerLifecycle(definition: CompositeTriggerDefinition, kind: string, details: Record<string, unknown>): Promise<void>;
22
+ auditMatch(record: TriggerMatchRecord, kind: 'trigger.partial' | 'trigger.matched' | 'trigger.fired' | 'trigger.emitted' | 'trigger.expired' | 'trigger.correlation', details?: Record<string, unknown>): Promise<void>;
23
+ }
24
+ export {};