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,24 +1,71 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { evaluatePatternV2, patternV2CandidateSignature, } from '../patternV2/evaluator.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
|
-
import {
|
|
5
|
-
import { asSourceEvent, clauseMatches, deterministicKeyForClause, expressionSatisfied, matchesCorrelationKey, sameEventIdentity, semanticSource, } from './matching.js';
|
|
4
|
+
import { asSourceEvent, clauseMatches, sameEventIdentity, } from './matching.js';
|
|
6
5
|
import { assertNoDerivedEventCycle } from './derivedGraph.js';
|
|
7
6
|
import { CompositeTriggerRuntimeSupport, } from './runtimeSupport.js';
|
|
7
|
+
function getByPath(source, path) {
|
|
8
|
+
let current = source;
|
|
9
|
+
for (const part of path.split('.')) {
|
|
10
|
+
if (current === null ||
|
|
11
|
+
typeof current !== 'object' ||
|
|
12
|
+
Array.isArray(current))
|
|
13
|
+
return undefined;
|
|
14
|
+
current = current[part];
|
|
15
|
+
}
|
|
16
|
+
return current;
|
|
17
|
+
}
|
|
18
|
+
function sourceEventOrder(left, right) {
|
|
19
|
+
return (Date.parse(left.occurredAt) - Date.parse(right.occurredAt) ||
|
|
20
|
+
left.sourceEventId.localeCompare(right.sourceEventId) ||
|
|
21
|
+
left.clauseId.localeCompare(right.clauseId));
|
|
22
|
+
}
|
|
23
|
+
function stablePatternValue(value) {
|
|
24
|
+
if (Array.isArray(value))
|
|
25
|
+
return value.map(stablePatternValue);
|
|
26
|
+
if (value && typeof value === 'object') {
|
|
27
|
+
return Object.fromEntries(Object.keys(value)
|
|
28
|
+
.sort()
|
|
29
|
+
.map((key) => [
|
|
30
|
+
key,
|
|
31
|
+
stablePatternValue(value[key]),
|
|
32
|
+
]));
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function patternPhysicalIdentity(event) {
|
|
37
|
+
return [
|
|
38
|
+
event.serverId ?? '-',
|
|
39
|
+
event.sourceEventId,
|
|
40
|
+
JSON.stringify(stablePatternValue(event.subscriptionArguments ?? {})),
|
|
41
|
+
].join(':');
|
|
42
|
+
}
|
|
8
43
|
export class CompositeTriggerEngine {
|
|
9
44
|
store;
|
|
10
45
|
evaluator;
|
|
11
46
|
now;
|
|
12
47
|
support;
|
|
13
|
-
|
|
48
|
+
workerId;
|
|
49
|
+
partitionLeaseMs;
|
|
50
|
+
constructor(store, evaluator = null, now = () => new Date(), options = {}) {
|
|
14
51
|
this.store = store;
|
|
15
52
|
this.evaluator = evaluator;
|
|
16
53
|
this.now = now;
|
|
17
54
|
this.support = new CompositeTriggerRuntimeSupport(store, now);
|
|
55
|
+
this.workerId =
|
|
56
|
+
String(options.workerId || 'event-intelligence-engine');
|
|
57
|
+
this.partitionLeaseMs = Math.max(1000, Number(options.partitionLeaseMs) || 300000);
|
|
18
58
|
}
|
|
19
59
|
async register(definitionInput) {
|
|
20
60
|
const definition = CompositeTriggerDefinitionSchema.parse(definitionInput);
|
|
21
|
-
|
|
61
|
+
const existingDefinitions = await this.store.listTriggers();
|
|
62
|
+
const stateByTrigger = new Map();
|
|
63
|
+
if (this.store.getTriggerState) {
|
|
64
|
+
await Promise.all(existingDefinitions.map(async (existing) => {
|
|
65
|
+
stateByTrigger.set(`${existing.triggerId}@${existing.version}`, await this.store.getTriggerState(existing.triggerId, existing.version));
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
assertNoDerivedEventCycle(existingDefinitions, definition, (triggerId, version) => stateByTrigger.get(`${triggerId}@${version}`) ?? null);
|
|
22
69
|
await this.store.putTrigger(definition);
|
|
23
70
|
return definition;
|
|
24
71
|
}
|
|
@@ -28,17 +75,33 @@ export class CompositeTriggerEngine {
|
|
|
28
75
|
return this.ingestTimerEvent(event);
|
|
29
76
|
}
|
|
30
77
|
const results = [];
|
|
31
|
-
for (const definition of this.store.listTriggers()) {
|
|
32
|
-
const triggerState = this.store.getTriggerState
|
|
78
|
+
for (const definition of await this.store.listTriggers()) {
|
|
79
|
+
const triggerState = this.store.getTriggerState
|
|
80
|
+
? await this.store.getTriggerState(definition.triggerId, definition.version)
|
|
81
|
+
: null;
|
|
33
82
|
if (!(await this.support.lifecycleAllows(definition, triggerState, this.now()))) {
|
|
34
83
|
continue;
|
|
35
84
|
}
|
|
36
85
|
const matchingClauses = definition.clauses.filter((clause) => clauseMatches(clause, event));
|
|
37
86
|
if (matchingClauses.length === 0)
|
|
38
87
|
continue;
|
|
39
|
-
const replayed = this.store
|
|
40
|
-
.listTriggerMatches(definition.triggerId)
|
|
41
|
-
.
|
|
88
|
+
const replayed = (await this.store
|
|
89
|
+
.listTriggerMatches(definition.triggerId))
|
|
90
|
+
.filter((record) => record.triggerVersion === definition.version &&
|
|
91
|
+
record.sourceEvents.some((source) => sameEventIdentity(source, event)))
|
|
92
|
+
.sort((a, b) => {
|
|
93
|
+
const rank = (status) => status === 'fired'
|
|
94
|
+
? 4
|
|
95
|
+
: status === 'emitted'
|
|
96
|
+
? 3
|
|
97
|
+
: status === 'matched'
|
|
98
|
+
? 2
|
|
99
|
+
: status === 'partial'
|
|
100
|
+
? 1
|
|
101
|
+
: 0;
|
|
102
|
+
return (rank(b.status) - rank(a.status) ||
|
|
103
|
+
Date.parse(b.updatedAt) - Date.parse(a.updatedAt));
|
|
104
|
+
})[0];
|
|
42
105
|
if (replayed) {
|
|
43
106
|
results.push({
|
|
44
107
|
triggerId: definition.triggerId,
|
|
@@ -50,13 +113,12 @@ export class CompositeTriggerEngine {
|
|
|
50
113
|
});
|
|
51
114
|
continue;
|
|
52
115
|
}
|
|
53
|
-
|
|
54
|
-
results.push(await this.applyEvent(definition, clause, event));
|
|
116
|
+
results.push(...await this.applyPattern(definition, matchingClauses, event));
|
|
55
117
|
}
|
|
56
118
|
return results;
|
|
57
119
|
}
|
|
58
120
|
async markFired(matchId, wakeId) {
|
|
59
|
-
const latest = this.latestMatch(matchId);
|
|
121
|
+
const latest = await this.latestMatch(matchId);
|
|
60
122
|
if (!latest)
|
|
61
123
|
throw new Error(`Unknown trigger match: ${matchId}`);
|
|
62
124
|
if (latest.status === 'fired')
|
|
@@ -77,7 +139,7 @@ export class CompositeTriggerEngine {
|
|
|
77
139
|
return fired;
|
|
78
140
|
}
|
|
79
141
|
async markDerivedEmitted(matchId, derivedEventId) {
|
|
80
|
-
const latest = this.latestMatch(matchId);
|
|
142
|
+
const latest = await this.latestMatch(matchId);
|
|
81
143
|
if (!latest)
|
|
82
144
|
throw new Error(`Unknown trigger match: ${matchId}`);
|
|
83
145
|
if (latest.status === 'emitted' &&
|
|
@@ -107,18 +169,20 @@ export class CompositeTriggerEngine {
|
|
|
107
169
|
const deadlineId = String(event.data.deadlineId ?? '');
|
|
108
170
|
if (!deadlineId || !this.store.getTemporalDeadline)
|
|
109
171
|
return [];
|
|
110
|
-
const deadline = this.store.getTemporalDeadline(deadlineId);
|
|
172
|
+
const deadline = await this.store.getTemporalDeadline(deadlineId);
|
|
111
173
|
if (!deadline || deadline.status !== 'pending')
|
|
112
174
|
return [];
|
|
113
|
-
const definition = this.store.listTriggers().find((candidate) => candidate.triggerId === deadline.triggerId &&
|
|
175
|
+
const definition = (await this.store.listTriggers()).find((candidate) => candidate.triggerId === deadline.triggerId &&
|
|
114
176
|
candidate.version === deadline.triggerVersion);
|
|
115
177
|
if (!definition)
|
|
116
178
|
return [];
|
|
117
|
-
const triggerState = this.store.getTriggerState
|
|
179
|
+
const triggerState = this.store.getTriggerState
|
|
180
|
+
? await this.store.getTriggerState(definition.triggerId, definition.version)
|
|
181
|
+
: null;
|
|
118
182
|
if (!(await this.support.lifecycleAllows(definition, triggerState, this.now()))) {
|
|
119
183
|
return [];
|
|
120
184
|
}
|
|
121
|
-
const match = this.latestMatch(deadline.matchId);
|
|
185
|
+
const match = await this.latestMatch(deadline.matchId);
|
|
122
186
|
if (!match ||
|
|
123
187
|
match.status === 'matched' ||
|
|
124
188
|
match.status === 'emitted' ||
|
|
@@ -126,224 +190,485 @@ export class CompositeTriggerEngine {
|
|
|
126
190
|
match.status === 'expired') {
|
|
127
191
|
return [];
|
|
128
192
|
}
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
193
|
+
if (match.patternState?.version === '2' &&
|
|
194
|
+
match.patternState.role === 'buffer') {
|
|
195
|
+
return this.evaluatePatternBuffer(definition, match, new Date(event.occurredAt));
|
|
196
|
+
}
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
patternPartitionKey(definition, matchingClauses, event) {
|
|
200
|
+
const dimensions = definition.pattern?.partitionBy ?? [];
|
|
201
|
+
if (!dimensions.length)
|
|
202
|
+
return null;
|
|
203
|
+
const clauseIds = new Set(matchingClauses.map((clause) => clause.id));
|
|
204
|
+
const components = [];
|
|
205
|
+
for (const dimension of dimensions) {
|
|
206
|
+
const fields = dimension.fields.filter((field) => clauseIds.has(field.ref));
|
|
207
|
+
if (!fields.length) {
|
|
208
|
+
throw new Error(`Pattern partition ${dimension.key} has no field for incoming event ${event.name}`);
|
|
209
|
+
}
|
|
210
|
+
const values = fields.map((field) => getByPath(event.data, field.path));
|
|
211
|
+
const first = values[0];
|
|
212
|
+
if (first === undefined ||
|
|
213
|
+
first === null ||
|
|
214
|
+
!['string', 'number', 'boolean'].includes(typeof first)) {
|
|
215
|
+
throw new Error(`Pattern partition ${dimension.key} resolved to a non-scalar value`);
|
|
216
|
+
}
|
|
217
|
+
if (values.some((value) => typeof value !== typeof first ||
|
|
218
|
+
value !== first)) {
|
|
219
|
+
throw new Error(`Pattern partition ${dimension.key} is ambiguous for incoming event ${event.name}`);
|
|
220
|
+
}
|
|
221
|
+
components.push({
|
|
222
|
+
key: dimension.key,
|
|
223
|
+
type: typeof first,
|
|
224
|
+
value: first,
|
|
139
225
|
});
|
|
140
|
-
return [{
|
|
141
|
-
triggerId: definition.triggerId,
|
|
142
|
-
match: expired,
|
|
143
|
-
matched: false,
|
|
144
|
-
fired: false,
|
|
145
|
-
}];
|
|
146
226
|
}
|
|
147
|
-
|
|
148
|
-
return [result];
|
|
227
|
+
return JSON.stringify(components);
|
|
149
228
|
}
|
|
150
|
-
async
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
229
|
+
async patternBufferId(definition, key) {
|
|
230
|
+
return `tm_${(await sha256Hex([
|
|
231
|
+
definition.triggerId,
|
|
232
|
+
definition.version,
|
|
233
|
+
key ?? '-',
|
|
234
|
+
'pattern-v2-buffer',
|
|
235
|
+
].join(':'))).slice(0, 24)}`;
|
|
236
|
+
}
|
|
237
|
+
patternDecisionRecord(item, evaluatedAt) {
|
|
238
|
+
const decision = item.decision;
|
|
239
|
+
return {
|
|
240
|
+
nodeId: item.nodeId,
|
|
241
|
+
decision: {
|
|
242
|
+
evaluator: decision.evaluator,
|
|
243
|
+
outcome: decision.outcome,
|
|
244
|
+
probability: decision.probability,
|
|
245
|
+
matched: decision.matched,
|
|
246
|
+
shouldEscalate: decision.shouldEscalate,
|
|
247
|
+
inputFields: decision.condition.input,
|
|
248
|
+
...(decision.metadata
|
|
249
|
+
? { providerEvidence: decision.metadata }
|
|
250
|
+
: {}),
|
|
251
|
+
evaluatedAt,
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
async applyPattern(definition, matchingClauses, event) {
|
|
256
|
+
const key = this.patternPartitionKey(definition, matchingClauses, event);
|
|
257
|
+
const capabilities = this.store.storeCapabilities?.();
|
|
258
|
+
const distributedLeases = capabilities?.partitionLeases === 'distributed-atomic';
|
|
259
|
+
if (!distributedLeases) {
|
|
260
|
+
return this.applyPatternWithPartition(definition, matchingClauses, event, key);
|
|
167
261
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
});
|
|
175
|
-
await this.store.appendTriggerMatch(expired);
|
|
176
|
-
await this.support.cancelDeadlinesForMatch(expired.matchId);
|
|
177
|
-
await this.support.auditMatch(expired, 'trigger.expired', {
|
|
178
|
-
reason: 'window_expired_before_event',
|
|
179
|
-
});
|
|
180
|
-
record = await this.newMatch(definition, key, event);
|
|
262
|
+
if (!this.store.claimPartitionLease ||
|
|
263
|
+
!this.store.releasePartitionLease) {
|
|
264
|
+
const error = new Error('Distributed Pattern v2 store is missing partition lease methods');
|
|
265
|
+
error.code =
|
|
266
|
+
'PATTERN_PARTITION_LEASE_UNSUPPORTED';
|
|
267
|
+
throw error;
|
|
181
268
|
}
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
269
|
+
const leaseKey = [
|
|
270
|
+
definition.triggerId,
|
|
271
|
+
definition.version,
|
|
272
|
+
key ?? '-',
|
|
273
|
+
].join(':');
|
|
274
|
+
const nowIso = this.now().toISOString();
|
|
275
|
+
const lease = await this.store.claimPartitionLease(leaseKey, {
|
|
276
|
+
workerId: this.workerId,
|
|
277
|
+
now: nowIso,
|
|
278
|
+
leaseMs: this.partitionLeaseMs,
|
|
279
|
+
});
|
|
280
|
+
if (!lease) {
|
|
281
|
+
const error = new Error(`Pattern partition is currently owned by another worker: ${leaseKey}`);
|
|
282
|
+
error.code =
|
|
283
|
+
'PATTERN_PARTITION_LEASE_BUSY';
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
return await this.applyPatternWithPartition(definition, matchingClauses, event, key);
|
|
193
288
|
}
|
|
194
|
-
|
|
195
|
-
await this.store.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
sourceEventId: event.sourceEventId,
|
|
289
|
+
finally {
|
|
290
|
+
await this.store.releasePartitionLease(leaseKey, {
|
|
291
|
+
workerId: this.workerId,
|
|
292
|
+
now: this.now().toISOString(),
|
|
199
293
|
});
|
|
200
|
-
return {
|
|
201
|
-
triggerId: definition.triggerId,
|
|
202
|
-
match: record,
|
|
203
|
-
matched: false,
|
|
204
|
-
fired: false,
|
|
205
|
-
};
|
|
206
294
|
}
|
|
207
|
-
return this.finalizeEligible(definition, record, this.now());
|
|
208
295
|
}
|
|
209
|
-
async
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
match: expired,
|
|
227
|
-
matched: false,
|
|
228
|
-
fired: false,
|
|
229
|
-
};
|
|
296
|
+
async applyPatternWithPartition(definition, matchingClauses, event, key) {
|
|
297
|
+
const pattern = definition.pattern;
|
|
298
|
+
if (!pattern)
|
|
299
|
+
return [];
|
|
300
|
+
const now = this.now();
|
|
301
|
+
const nowIso = now.toISOString();
|
|
302
|
+
const bufferId = await this.patternBufferId(definition, key);
|
|
303
|
+
const existing = await this.latestMatch(bufferId);
|
|
304
|
+
let sourceEvents = existing?.sourceEvents
|
|
305
|
+
? [...existing.sourceEvents]
|
|
306
|
+
: [];
|
|
307
|
+
for (const clause of matchingClauses) {
|
|
308
|
+
const duplicate = sourceEvents.some((source) => source.clauseId === clause.id &&
|
|
309
|
+
sameEventIdentity(source, event));
|
|
310
|
+
if (!duplicate) {
|
|
311
|
+
sourceEvents.push(asSourceEvent(clause.id, event));
|
|
312
|
+
}
|
|
230
313
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
314
|
+
sourceEvents.sort(sourceEventOrder);
|
|
315
|
+
const previousMaxObserved = existing?.patternState
|
|
316
|
+
?.maxObservedOccurredAt
|
|
317
|
+
? Date.parse(existing.patternState.maxObservedOccurredAt)
|
|
318
|
+
: Number.NEGATIVE_INFINITY;
|
|
319
|
+
const maxObserved = Math.max(previousMaxObserved, Date.parse(event.occurredAt), ...sourceEvents
|
|
320
|
+
.map((source) => Date.parse(source.occurredAt))
|
|
321
|
+
.filter(Number.isFinite));
|
|
322
|
+
const allowedLatenessMs = definition.eventTime?.allowedLatenessMs ?? 0;
|
|
323
|
+
const watermarkMs = maxObserved - allowedLatenessMs;
|
|
324
|
+
const retentionFloor = watermarkMs - definition.withinMs;
|
|
325
|
+
sourceEvents = sourceEvents.filter((source) => Date.parse(source.occurredAt) >= retentionFloor);
|
|
326
|
+
const bufferedPhysicalEvents = new Set(sourceEvents.map(patternPhysicalIdentity)).size;
|
|
327
|
+
if (bufferedPhysicalEvents > pattern.execution.maxBufferedEvents) {
|
|
328
|
+
await this.store.appendAudit({
|
|
329
|
+
auditId: `audit_${(await sha256Hex(`${definition.triggerId}:${definition.version}:pattern_buffer_overflow:${key ?? '-'}:${nowIso}`)).slice(0, 24)}`,
|
|
330
|
+
traceId: event.traceId,
|
|
331
|
+
timestamp: nowIso,
|
|
332
|
+
kind: 'event.buffer_overflow',
|
|
333
|
+
entityType: 'trigger',
|
|
334
|
+
entityId: definition.triggerId,
|
|
335
|
+
details: {
|
|
336
|
+
triggerVersion: definition.version,
|
|
337
|
+
patternVersion: '2',
|
|
338
|
+
partitionKey: key,
|
|
339
|
+
bufferedEvents: bufferedPhysicalEvents,
|
|
340
|
+
maxBufferedEvents: pattern.execution.maxBufferedEvents,
|
|
341
|
+
},
|
|
241
342
|
});
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
fired: false,
|
|
247
|
-
};
|
|
343
|
+
const error = new Error(`Pattern v2 buffer exceeded maxBufferedEvents=${pattern.execution.maxBufferedEvents}`);
|
|
344
|
+
error.code =
|
|
345
|
+
'PATTERN_V2_BUFFER_LIMIT_EXCEEDED';
|
|
346
|
+
throw error;
|
|
248
347
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
await
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
type: 'semantic_boolean',
|
|
266
|
-
instruction: semantic.instruction,
|
|
267
|
-
input: semantic.input,
|
|
268
|
-
matchThreshold: semantic.matchThreshold,
|
|
269
|
-
rejectThreshold: semantic.rejectThreshold,
|
|
270
|
-
uncertain: semantic.uncertain,
|
|
271
|
-
});
|
|
272
|
-
record = TriggerMatchRecordSchema.parse({
|
|
273
|
-
...record,
|
|
274
|
-
correlationDecision: {
|
|
275
|
-
evaluator: decision.evaluator,
|
|
276
|
-
outcome: decision.outcome,
|
|
277
|
-
probability: decision.probability,
|
|
278
|
-
matched: decision.matched,
|
|
279
|
-
shouldEscalate: decision.shouldEscalate,
|
|
280
|
-
inputFields: semantic.input,
|
|
281
|
-
...(decision.metadata ? { providerEvidence: decision.metadata } : {}),
|
|
282
|
-
evaluatedAt: nowIso,
|
|
348
|
+
if (Date.parse(event.occurredAt) < retentionFloor &&
|
|
349
|
+
!existing?.sourceEvents.some((source) => sameEventIdentity(source, event))) {
|
|
350
|
+
await this.store.appendAudit({
|
|
351
|
+
auditId: `audit_${(await sha256Hex(`${definition.triggerId}:${definition.version}:pattern_late_event_dropped:${event.sourceEventId}:${nowIso}`)).slice(0, 24)}`,
|
|
352
|
+
traceId: event.traceId,
|
|
353
|
+
timestamp: nowIso,
|
|
354
|
+
kind: 'event.late_dropped',
|
|
355
|
+
entityType: 'trigger',
|
|
356
|
+
entityId: definition.triggerId,
|
|
357
|
+
details: {
|
|
358
|
+
triggerVersion: definition.version,
|
|
359
|
+
patternVersion: '2',
|
|
360
|
+
sourceEventId: event.sourceEventId,
|
|
361
|
+
occurredAt: event.occurredAt,
|
|
362
|
+
watermarkAt: new Date(watermarkMs).toISOString(),
|
|
363
|
+
retentionFloor: new Date(retentionFloor).toISOString(),
|
|
283
364
|
},
|
|
284
|
-
updatedAt: nowIso,
|
|
285
365
|
});
|
|
286
|
-
|
|
287
|
-
await this.support.auditMatch(record, 'trigger.correlation', {
|
|
288
|
-
outcome: decision.outcome,
|
|
289
|
-
probability: decision.probability,
|
|
290
|
-
evaluator: decision.evaluator,
|
|
291
|
-
});
|
|
292
|
-
if (!decision.matched) {
|
|
293
|
-
return {
|
|
366
|
+
return [{
|
|
294
367
|
triggerId: definition.triggerId,
|
|
295
|
-
match:
|
|
368
|
+
match: existing ?? null,
|
|
296
369
|
matched: false,
|
|
297
370
|
fired: false,
|
|
298
|
-
};
|
|
299
|
-
}
|
|
371
|
+
}];
|
|
300
372
|
}
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
status: 'matched',
|
|
304
|
-
updatedAt: nowIso,
|
|
305
|
-
});
|
|
306
|
-
await this.store.appendTriggerMatch(matched);
|
|
307
|
-
await this.support.auditMatch(matched, 'trigger.matched', {
|
|
308
|
-
sourceEventIds: matched.sourceEvents.map((item) => item.sourceEventId),
|
|
309
|
-
temporal: temporal.conditionStates,
|
|
310
|
-
});
|
|
311
|
-
return {
|
|
312
|
-
triggerId: definition.triggerId,
|
|
313
|
-
match: matched,
|
|
314
|
-
matched: true,
|
|
315
|
-
fired: false,
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
async newMatch(definition, key, event) {
|
|
319
|
-
const openedAt = event.occurredAt;
|
|
320
|
-
const expiresAt = new Date(Date.parse(openedAt) + definition.withinMs).toISOString();
|
|
321
|
-
const matchId = `tm_${(await sha256Hex([
|
|
322
|
-
definition.triggerId,
|
|
323
|
-
definition.version,
|
|
324
|
-
key ?? '-',
|
|
325
|
-
event.sourceEventId,
|
|
326
|
-
].join(':'))).slice(0, 24)}`;
|
|
327
|
-
return TriggerMatchRecordSchema.parse({
|
|
373
|
+
const openedAt = sourceEvents[0]?.occurredAt ?? event.occurredAt;
|
|
374
|
+
const buffer = TriggerMatchRecordSchema.parse({
|
|
328
375
|
protocolVersion: COMPOSITE_TRIGGER_PROTOCOL_VERSION,
|
|
329
376
|
schemaVersion: COMPOSITE_TRIGGER_SCHEMA_VERSION,
|
|
330
|
-
matchId,
|
|
377
|
+
matchId: bufferId,
|
|
331
378
|
triggerId: definition.triggerId,
|
|
332
379
|
triggerVersion: definition.version,
|
|
333
380
|
status: 'partial',
|
|
334
|
-
|
|
381
|
+
partitionKey: key,
|
|
335
382
|
openedAt,
|
|
336
|
-
expiresAt,
|
|
337
|
-
updatedAt:
|
|
338
|
-
sourceEvents
|
|
339
|
-
|
|
383
|
+
expiresAt: new Date(Date.parse(openedAt) + definition.withinMs).toISOString(),
|
|
384
|
+
updatedAt: nowIso,
|
|
385
|
+
sourceEvents,
|
|
386
|
+
semanticDecision: null,
|
|
387
|
+
patternState: {
|
|
388
|
+
version: '2',
|
|
389
|
+
role: 'buffer',
|
|
390
|
+
maxObservedOccurredAt: new Date(maxObserved).toISOString(),
|
|
391
|
+
semanticDecisions: [],
|
|
392
|
+
},
|
|
340
393
|
firedWakeId: null,
|
|
341
394
|
derivedEventIds: [],
|
|
342
395
|
});
|
|
396
|
+
await this.store.appendTriggerMatch(buffer);
|
|
397
|
+
return this.evaluatePatternBuffer(definition, buffer, now);
|
|
398
|
+
}
|
|
399
|
+
candidateFitsTriggerWindow(candidate, withinMs) {
|
|
400
|
+
if (candidate.events.length < 2)
|
|
401
|
+
return true;
|
|
402
|
+
const times = candidate.events
|
|
403
|
+
.map((event) => Date.parse(event.occurredAt))
|
|
404
|
+
.filter(Number.isFinite);
|
|
405
|
+
if (times.length < 2)
|
|
406
|
+
return true;
|
|
407
|
+
return Math.max(...times) - Math.min(...times) <= withinMs;
|
|
408
|
+
}
|
|
409
|
+
async evaluatePatternBuffer(definition, buffer, evaluationNow) {
|
|
410
|
+
const pattern = definition.pattern;
|
|
411
|
+
if (!pattern)
|
|
412
|
+
return [];
|
|
413
|
+
const nowIso = evaluationNow.toISOString();
|
|
414
|
+
const semanticCache = typeof this.store.getSemanticDecisionCache === 'function' &&
|
|
415
|
+
typeof this.store.putSemanticDecisionCache === 'function'
|
|
416
|
+
? {
|
|
417
|
+
get: (key) => this.store.getSemanticDecisionCache(key),
|
|
418
|
+
set: (key, decision) => this.store.putSemanticDecisionCache(key, decision),
|
|
419
|
+
}
|
|
420
|
+
: null;
|
|
421
|
+
const reachedDeadlines = this.store.listTemporalDeadlines
|
|
422
|
+
? (await this.store.listTemporalDeadlines({
|
|
423
|
+
matchId: buffer.matchId,
|
|
424
|
+
status: 'pending',
|
|
425
|
+
}))
|
|
426
|
+
.filter((deadline) => Date.parse(deadline.dueAt) <= evaluationNow.getTime())
|
|
427
|
+
: [];
|
|
428
|
+
const evaluation = await evaluatePatternV2({
|
|
429
|
+
definition: pattern,
|
|
430
|
+
events: buffer.sourceEvents,
|
|
431
|
+
evaluator: this.evaluator,
|
|
432
|
+
now: evaluationNow,
|
|
433
|
+
allowedLatenessMs: definition.eventTime?.allowedLatenessMs ?? 0,
|
|
434
|
+
semanticCache,
|
|
435
|
+
semanticCacheNamespace: `${definition.triggerId}@${definition.version}`,
|
|
436
|
+
});
|
|
437
|
+
for (const semantic of evaluation.semanticTrace) {
|
|
438
|
+
await this.support.auditMatch(buffer, 'trigger.correlation', {
|
|
439
|
+
patternVersion: '2',
|
|
440
|
+
semanticNodeId: semantic.nodeId,
|
|
441
|
+
candidateSignature: semantic.candidateSignature,
|
|
442
|
+
evaluator: semantic.decision.evaluator,
|
|
443
|
+
outcome: semantic.decision.outcome,
|
|
444
|
+
probability: semantic.decision.probability,
|
|
445
|
+
matched: semantic.decision.matched,
|
|
446
|
+
shouldEscalate: semantic.decision.shouldEscalate,
|
|
447
|
+
semanticCacheHits: evaluation.semanticCacheHits,
|
|
448
|
+
semanticCacheMisses: evaluation.semanticCacheMisses,
|
|
449
|
+
inputFields: semantic.decision.condition.input,
|
|
450
|
+
...(semantic.decision.metadata
|
|
451
|
+
? { providerEvidence: semantic.decision.metadata }
|
|
452
|
+
: {}),
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
await this.support.cancelDeadlinesForMatch(buffer.matchId);
|
|
456
|
+
if (evaluation.pending.length) {
|
|
457
|
+
const deadlines = evaluation.pending
|
|
458
|
+
.filter((candidate) => candidate.pendingUntil &&
|
|
459
|
+
this.candidateFitsTriggerWindow(candidate, definition.withinMs))
|
|
460
|
+
.map((candidate) => ({
|
|
461
|
+
conditionId: `pattern-v2:${patternV2CandidateSignature(candidate)}`,
|
|
462
|
+
dueAt: candidate.pendingUntil,
|
|
463
|
+
}));
|
|
464
|
+
await this.support.scheduleDeadlines(definition, buffer, deadlines);
|
|
465
|
+
}
|
|
466
|
+
const previouslyEmitted = new Set((await this.store
|
|
467
|
+
.listTriggerMatches(definition.triggerId))
|
|
468
|
+
.filter((record) => record.triggerVersion === definition.version &&
|
|
469
|
+
record.patternState?.version === '2' &&
|
|
470
|
+
record.patternState.role === 'match' &&
|
|
471
|
+
record.patternState.signature)
|
|
472
|
+
.map((record) => record.patternState.signature));
|
|
473
|
+
const fresh = evaluation.matches
|
|
474
|
+
.filter((candidate) => this.candidateFitsTriggerWindow(candidate, definition.withinMs))
|
|
475
|
+
.map((candidate, rank) => ({ candidate, rank }))
|
|
476
|
+
.filter(({ candidate }) => candidate.events.length > 0 &&
|
|
477
|
+
!previouslyEmitted.has(patternV2CandidateSignature(candidate)))
|
|
478
|
+
.sort((left, right) => left.candidate.startIndex - right.candidate.startIndex ||
|
|
479
|
+
left.rank - right.rank)
|
|
480
|
+
.map(({ candidate }) => candidate);
|
|
481
|
+
const selected = [];
|
|
482
|
+
const occupied = new Set();
|
|
483
|
+
let selectionLimit = pattern.selection.maxMatchesPerEvent;
|
|
484
|
+
const hasEffect = Boolean(definition.target || definition.derivedEvent);
|
|
485
|
+
if (hasEffect) {
|
|
486
|
+
const triggerState = this.store.getTriggerState
|
|
487
|
+
? await this.store.getTriggerState(definition.triggerId, definition.version)
|
|
488
|
+
: null;
|
|
489
|
+
const lifecycle = definition.lifecycle ?? {};
|
|
490
|
+
if (lifecycle.oneShot || lifecycle.completeOnGoal) {
|
|
491
|
+
selectionLimit = Math.min(selectionLimit, 1);
|
|
492
|
+
}
|
|
493
|
+
if (lifecycle.maxFirings !== undefined) {
|
|
494
|
+
selectionLimit = Math.min(selectionLimit, Math.max(0, lifecycle.maxFirings - (triggerState?.fireCount ?? 0)));
|
|
495
|
+
}
|
|
496
|
+
if ((lifecycle.cooldownMs ?? 0) > 0) {
|
|
497
|
+
selectionLimit = Math.min(selectionLimit, 1);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
const indexed = [...buffer.sourceEvents].sort(sourceEventOrder);
|
|
501
|
+
const indexByIdentity = new Map(indexed.map((source, index) => [
|
|
502
|
+
patternPhysicalIdentity(source),
|
|
503
|
+
index,
|
|
504
|
+
]));
|
|
505
|
+
let skipPastLastThrough = -1;
|
|
506
|
+
let skipBefore = -1;
|
|
507
|
+
const skipStarts = new Set();
|
|
508
|
+
const bindingBoundary = (candidate, ref, edge) => {
|
|
509
|
+
const events = [...(candidate.bindings[ref] ?? [])]
|
|
510
|
+
.sort(sourceEventOrder);
|
|
511
|
+
const event = edge === 'first' ? events[0] : events.at(-1);
|
|
512
|
+
if (!event)
|
|
513
|
+
return null;
|
|
514
|
+
return indexByIdentity.get(patternPhysicalIdentity(event)) ?? null;
|
|
515
|
+
};
|
|
516
|
+
for (const candidate of fresh) {
|
|
517
|
+
if (selected.length >= selectionLimit)
|
|
518
|
+
break;
|
|
519
|
+
if (candidate.startIndex <= skipPastLastThrough)
|
|
520
|
+
continue;
|
|
521
|
+
if (candidate.startIndex < skipBefore)
|
|
522
|
+
continue;
|
|
523
|
+
if (skipStarts.has(candidate.startIndex))
|
|
524
|
+
continue;
|
|
525
|
+
const identities = candidate.events.map(patternPhysicalIdentity);
|
|
526
|
+
if (pattern.selection.overlap === 'disallow' &&
|
|
527
|
+
identities.some((identity) => occupied.has(identity))) {
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
selected.push(candidate);
|
|
531
|
+
identities.forEach((identity) => occupied.add(identity));
|
|
532
|
+
const afterMatch = pattern.selection.afterMatch;
|
|
533
|
+
if (afterMatch === 'skipPastLast') {
|
|
534
|
+
skipPastLastThrough = Math.max(skipPastLastThrough, candidate.endIndex);
|
|
535
|
+
}
|
|
536
|
+
else if (afterMatch === 'skipToNext') {
|
|
537
|
+
skipStarts.add(candidate.startIndex);
|
|
538
|
+
}
|
|
539
|
+
else if (typeof afterMatch === 'object') {
|
|
540
|
+
const boundary = bindingBoundary(candidate, afterMatch.ref, afterMatch.kind === 'skipToFirst' ? 'first' : 'last');
|
|
541
|
+
if (boundary !== null) {
|
|
542
|
+
skipBefore = Math.max(skipBefore, boundary);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
const results = [];
|
|
547
|
+
for (const candidate of selected) {
|
|
548
|
+
const signature = patternV2CandidateSignature(candidate);
|
|
549
|
+
const matchId = `tm_${(await sha256Hex([
|
|
550
|
+
definition.triggerId,
|
|
551
|
+
definition.version,
|
|
552
|
+
buffer.partitionKey ?? '-',
|
|
553
|
+
signature,
|
|
554
|
+
'pattern-v2-match',
|
|
555
|
+
].join(':'))).slice(0, 24)}`;
|
|
556
|
+
const openedAt = candidate.events[0]?.occurredAt ?? buffer.openedAt;
|
|
557
|
+
const semanticDecisions = candidate.semanticDecisions.map((item) => this.patternDecisionRecord(item, nowIso));
|
|
558
|
+
const sourceCompletionAt = candidate.events
|
|
559
|
+
.map((item) => Date.parse(item.occurredAt))
|
|
560
|
+
.filter(Number.isFinite)
|
|
561
|
+
.sort((a, b) => b - a)[0] ?? evaluationNow.getTime();
|
|
562
|
+
const deadlineCompletionAt = reachedDeadlines
|
|
563
|
+
.map((deadline) => Date.parse(deadline.dueAt))
|
|
564
|
+
.filter(Number.isFinite)
|
|
565
|
+
.sort((a, b) => b - a)[0];
|
|
566
|
+
const completedAt = new Date(deadlineCompletionAt ?? sourceCompletionAt).toISOString();
|
|
567
|
+
const lastSemantic = semanticDecisions.at(-1)?.decision ?? null;
|
|
568
|
+
const matched = TriggerMatchRecordSchema.parse({
|
|
569
|
+
protocolVersion: COMPOSITE_TRIGGER_PROTOCOL_VERSION,
|
|
570
|
+
schemaVersion: COMPOSITE_TRIGGER_SCHEMA_VERSION,
|
|
571
|
+
matchId,
|
|
572
|
+
triggerId: definition.triggerId,
|
|
573
|
+
triggerVersion: definition.version,
|
|
574
|
+
status: 'matched',
|
|
575
|
+
partitionKey: buffer.partitionKey,
|
|
576
|
+
openedAt,
|
|
577
|
+
expiresAt: new Date(Date.parse(openedAt) + definition.withinMs).toISOString(),
|
|
578
|
+
updatedAt: nowIso,
|
|
579
|
+
sourceEvents: candidate.events,
|
|
580
|
+
semanticDecision: lastSemantic,
|
|
581
|
+
patternState: {
|
|
582
|
+
version: '2',
|
|
583
|
+
role: 'match',
|
|
584
|
+
signature,
|
|
585
|
+
completedAt,
|
|
586
|
+
semanticDecisions,
|
|
587
|
+
},
|
|
588
|
+
firedWakeId: null,
|
|
589
|
+
derivedEventIds: [],
|
|
590
|
+
});
|
|
591
|
+
await this.store.appendTriggerMatch(matched);
|
|
592
|
+
await this.support.auditMatch(matched, 'trigger.matched', {
|
|
593
|
+
patternVersion: '2',
|
|
594
|
+
signature,
|
|
595
|
+
semanticEvaluations: evaluation.semanticEvaluations,
|
|
596
|
+
truncated: evaluation.truncated,
|
|
597
|
+
sourceEventIds: matched.sourceEvents.map((source) => source.sourceEventId),
|
|
598
|
+
});
|
|
599
|
+
results.push({
|
|
600
|
+
triggerId: definition.triggerId,
|
|
601
|
+
match: matched,
|
|
602
|
+
matched: true,
|
|
603
|
+
fired: false,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
if (selected.length) {
|
|
607
|
+
const selectedIndexes = selected.flatMap((candidate) => [
|
|
608
|
+
candidate.startIndex,
|
|
609
|
+
candidate.endIndex,
|
|
610
|
+
]).filter((index) => index >= 0);
|
|
611
|
+
const firstStart = selectedIndexes.length
|
|
612
|
+
? Math.min(...selected.map((candidate) => candidate.startIndex))
|
|
613
|
+
: -1;
|
|
614
|
+
const lastEnd = selectedIndexes.length
|
|
615
|
+
? Math.max(...selected.map((candidate) => candidate.endIndex))
|
|
616
|
+
: -1;
|
|
617
|
+
let retained = [...buffer.sourceEvents];
|
|
618
|
+
const shouldDropByIndex = (index) => {
|
|
619
|
+
const afterMatch = pattern.selection.afterMatch;
|
|
620
|
+
if (afterMatch === 'skipPastLast') {
|
|
621
|
+
return index <= lastEnd;
|
|
622
|
+
}
|
|
623
|
+
if (afterMatch === 'skipToNext') {
|
|
624
|
+
return index <= firstStart;
|
|
625
|
+
}
|
|
626
|
+
if (typeof afterMatch === 'object') {
|
|
627
|
+
const boundaries = selected
|
|
628
|
+
.map((candidate) => bindingBoundary(candidate, afterMatch.ref, afterMatch.kind === 'skipToFirst' ? 'first' : 'last'))
|
|
629
|
+
.filter((value) => value !== null);
|
|
630
|
+
if (!boundaries.length)
|
|
631
|
+
return false;
|
|
632
|
+
return index < Math.max(...boundaries);
|
|
633
|
+
}
|
|
634
|
+
return false;
|
|
635
|
+
};
|
|
636
|
+
const consumed = pattern.selection.overlap === 'disallow'
|
|
637
|
+
? new Set(selected
|
|
638
|
+
.flatMap((candidate) => candidate.events)
|
|
639
|
+
.map(patternPhysicalIdentity))
|
|
640
|
+
: new Set();
|
|
641
|
+
retained = indexed.filter((source, index) => !shouldDropByIndex(index) &&
|
|
642
|
+
!consumed.has(patternPhysicalIdentity(source)));
|
|
643
|
+
const openedAt = retained[0]?.occurredAt ?? evaluationNow.toISOString();
|
|
644
|
+
const updatedBuffer = TriggerMatchRecordSchema.parse({
|
|
645
|
+
...buffer,
|
|
646
|
+
openedAt,
|
|
647
|
+
expiresAt: new Date(Date.parse(openedAt) + definition.withinMs).toISOString(),
|
|
648
|
+
updatedAt: nowIso,
|
|
649
|
+
sourceEvents: retained,
|
|
650
|
+
});
|
|
651
|
+
await this.store.appendTriggerMatch(updatedBuffer);
|
|
652
|
+
}
|
|
653
|
+
if (!results.length) {
|
|
654
|
+
await this.support.auditMatch(buffer, 'trigger.partial', {
|
|
655
|
+
reason: 'pattern_v2_wait',
|
|
656
|
+
patternVersion: '2',
|
|
657
|
+
pendingCandidates: evaluation.pending.length,
|
|
658
|
+
semanticEvaluations: evaluation.semanticEvaluations,
|
|
659
|
+
truncated: evaluation.truncated,
|
|
660
|
+
});
|
|
661
|
+
return [{
|
|
662
|
+
triggerId: definition.triggerId,
|
|
663
|
+
match: buffer,
|
|
664
|
+
matched: false,
|
|
665
|
+
fired: false,
|
|
666
|
+
}];
|
|
667
|
+
}
|
|
668
|
+
return results;
|
|
343
669
|
}
|
|
344
|
-
latestMatch(matchId) {
|
|
345
|
-
return this.store
|
|
346
|
-
.listTriggerMatches()
|
|
670
|
+
async latestMatch(matchId) {
|
|
671
|
+
return (await this.store.listTriggerMatches())
|
|
347
672
|
.filter((record) => record.matchId === matchId)
|
|
348
673
|
.sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))[0] ?? null;
|
|
349
674
|
}
|