mcp-event-intelligence 0.3.1 → 0.5.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 +81 -0
- package/README.md +123 -23
- package/SECURITY.md +4 -4
- package/bin/mcp-event-intelligence.mjs +3 -3
- package/dist/src/composite/engine.d.ts +6 -0
- package/dist/src/composite/engine.js +599 -10
- package/dist/src/composite/matching.d.ts +1 -1
- package/dist/src/composite/matching.js +66 -1
- package/dist/src/intelligenceProtocol/activationSchemas.d.ts +2 -0
- package/dist/src/intelligenceProtocol/activationSchemas.js +1 -0
- package/dist/src/intelligenceProtocol/index.d.ts +9 -2
- package/dist/src/intelligenceProtocol/index.js +5 -1
- package/dist/src/intelligenceProtocol/operatorRegistry.d.ts +567 -0
- package/dist/src/intelligenceProtocol/operatorRegistry.js +477 -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 +6 -0
- package/dist/src/intelligenceProtocol/schemas.js +3 -0
- package/dist/src/intelligenceProtocol/triggerPlanSchemas.d.ts +179 -4
- package/dist/src/intelligenceProtocol/triggerPlanSchemas.js +38 -4
- package/dist/src/intelligenceProtocol/triggerSchemas.d.ts +262 -0
- package/dist/src/intelligenceProtocol/triggerSchemas.js +164 -2
- package/dist/src/mcpEvents/consumer.d.ts +2 -0
- package/dist/src/mcpEvents/consumer.js +2 -0
- package/dist/src/mcpEvents/erpnextProvider.d.ts +0 -2
- package/dist/src/mcpEvents/erpnextProvider.js +0 -3
- package/dist/src/mcpEvents/provider.d.ts +24 -10
- package/dist/src/mcpEvents/provider.js +124 -55
- package/dist/src/mcpEvents/server.d.ts +3 -1
- package/dist/src/mcpEvents/server.js +20 -19
- package/dist/src/patternV2/evaluator.d.ts +43 -0
- package/dist/src/patternV2/evaluator.js +927 -0
- package/dist/src/patternV2/legacyCompiler.d.ts +3 -0
- package/dist/src/patternV2/legacyCompiler.js +184 -0
- package/dist/src/protocol/types.d.ts +1 -0
- package/package.json +5 -2
- package/scripts/host-integration.d.mts +47 -0
- package/scripts/host-integration.mjs +9 -0
- package/scripts/lib/activation-hydrator.mjs +1 -0
- package/scripts/lib/composite-event-consumer.mjs +4 -0
- package/scripts/lib/derived-event-coordinator.mjs +75 -1
- package/scripts/lib/json-schema.mjs +134 -0
- package/scripts/lib/local-event-intelligence-runtime.mjs +1 -0
- package/scripts/lib/mcp-events-client.mjs +798 -253
- package/scripts/lib/mcp-events-compatibility.mjs +54 -0
- package/scripts/lib/persistent-event-store.mjs +68 -9
- package/scripts/lib/trigger-control-plane.mjs +180 -0
- package/scripts/lib/trigger-inspector.mjs +15 -0
- package/scripts/lib/trigger-planner.mjs +213 -12
- package/scripts/mcp-stdio-server.mjs +38 -52
- package/server.json +4 -4
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const MCP_EVENTS_EXTENSION_ID = 'io.modelcontextprotocol/events';
|
|
2
|
+
|
|
3
|
+
export const MCP_EVENTS_COMPATIBILITY_PROFILES = Object.freeze({
|
|
4
|
+
'experimental-extension-2026-09-25': Object.freeze({
|
|
5
|
+
id: 'experimental-extension-2026-09-25',
|
|
6
|
+
capabilityStyle: 'extension',
|
|
7
|
+
extensionId: MCP_EVENTS_EXTENSION_ID,
|
|
8
|
+
listMethod: 'events/list',
|
|
9
|
+
pollMethod: 'events/poll',
|
|
10
|
+
deliveryModes: Object.freeze(['poll', 'push', 'webhook']),
|
|
11
|
+
experimental: true,
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_MCP_EVENTS_COMPATIBILITY_PROFILE =
|
|
16
|
+
'experimental-extension-2026-09-25';
|
|
17
|
+
|
|
18
|
+
export function getMcpEventsCompatibilityProfile(profileId) {
|
|
19
|
+
const id =
|
|
20
|
+
!profileId || profileId === 'auto'
|
|
21
|
+
? DEFAULT_MCP_EVENTS_COMPATIBILITY_PROFILE
|
|
22
|
+
: String(profileId);
|
|
23
|
+
const profile = MCP_EVENTS_COMPATIBILITY_PROFILES[id];
|
|
24
|
+
if (!profile) {
|
|
25
|
+
const error = new Error(`Unknown MCP Events compatibility profile: ${id}`);
|
|
26
|
+
error.code = 'MCP_EVENTS_PROFILE_UNKNOWN';
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
return profile;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function profileAdvertisedByCapabilities(profile, capabilities = {}) {
|
|
33
|
+
if (profile.capabilityStyle !== 'extension') return false;
|
|
34
|
+
const extensions =
|
|
35
|
+
capabilities?.extensions &&
|
|
36
|
+
!Array.isArray(capabilities.extensions) &&
|
|
37
|
+
typeof capabilities.extensions === 'object'
|
|
38
|
+
? capabilities.extensions
|
|
39
|
+
: {};
|
|
40
|
+
return Object.prototype.hasOwnProperty.call(
|
|
41
|
+
extensions,
|
|
42
|
+
profile.extensionId,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolveMcpEventsCompatibilityProfile({
|
|
47
|
+
capabilities,
|
|
48
|
+
requested = 'auto',
|
|
49
|
+
} = {}) {
|
|
50
|
+
const profile = getMcpEventsCompatibilityProfile(requested);
|
|
51
|
+
return profileAdvertisedByCapabilities(profile, capabilities)
|
|
52
|
+
? profile
|
|
53
|
+
: null;
|
|
54
|
+
}
|
|
@@ -32,6 +32,22 @@ function scopeIdFromDirectoryName(name) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function canonicalStateValue(value) {
|
|
36
|
+
if (Array.isArray(value)) return value.map(canonicalStateValue);
|
|
37
|
+
if (value && typeof value === 'object') {
|
|
38
|
+
return Object.fromEntries(
|
|
39
|
+
Object.keys(value)
|
|
40
|
+
.sort()
|
|
41
|
+
.map((key) => [key, canonicalStateValue(value[key])]),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function mcpClientStateKey(connectionId, eventName, args = {}) {
|
|
48
|
+
return `${String(connectionId)}::${String(eventName)}::${JSON.stringify(canonicalStateValue(args ?? {}))}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
async function readJsonLines(filePath) {
|
|
36
52
|
try {
|
|
37
53
|
const raw = await readFile(filePath, 'utf8');
|
|
@@ -263,6 +279,7 @@ export class PersistentEventStore {
|
|
|
263
279
|
const record = {
|
|
264
280
|
sequence: Number(raw.sequence),
|
|
265
281
|
serverId: String(raw.serverId),
|
|
282
|
+
subscriptionId: raw.subscriptionId ? String(raw.subscriptionId) : null,
|
|
266
283
|
event: McpEventOccurrenceSchema.parse(raw.event),
|
|
267
284
|
};
|
|
268
285
|
if (!Number.isInteger(record.sequence) || record.sequence < 1) {
|
|
@@ -270,7 +287,7 @@ export class PersistentEventStore {
|
|
|
270
287
|
}
|
|
271
288
|
this.#mcpOccurrences.push(record);
|
|
272
289
|
this.#mcpOccurrenceKeys.add(
|
|
273
|
-
`${record.serverId}:${record.event.eventId}`,
|
|
290
|
+
`${record.serverId}:${record.subscriptionId ?? ''}:${record.event.eventId}`,
|
|
274
291
|
);
|
|
275
292
|
}
|
|
276
293
|
this.#mcpOccurrences.sort((a, b) => a.sequence - b.sequence);
|
|
@@ -282,15 +299,27 @@ export class PersistentEventStore {
|
|
|
282
299
|
connectionId: String(raw.connectionId),
|
|
283
300
|
serverId: String(raw.serverId || raw.connectionId),
|
|
284
301
|
eventName: String(raw.eventName),
|
|
302
|
+
arguments:
|
|
303
|
+
raw.arguments && !Array.isArray(raw.arguments) && typeof raw.arguments === 'object'
|
|
304
|
+
? raw.arguments
|
|
305
|
+
: {},
|
|
306
|
+
subscriptionId: raw.subscriptionId ? String(raw.subscriptionId) : null,
|
|
307
|
+
deliveryMode: raw.deliveryMode ? String(raw.deliveryMode) : 'poll',
|
|
285
308
|
cursor: raw.cursor === null || raw.cursor === undefined
|
|
286
309
|
? null
|
|
287
310
|
: String(raw.cursor),
|
|
288
311
|
updatedAt: String(raw.updatedAt || new Date(0).toISOString()),
|
|
312
|
+
...(raw.nextPollAt ? { nextPollAt: String(raw.nextPollAt) } : {}),
|
|
289
313
|
...(raw.lastEventAt ? { lastEventAt: String(raw.lastEventAt) } : {}),
|
|
290
314
|
...(raw.lastError ? { lastError: String(raw.lastError) } : {}),
|
|
315
|
+
...(typeof raw.truncated === 'boolean' ? { truncated: raw.truncated } : {}),
|
|
291
316
|
};
|
|
292
317
|
this.#mcpClientStates.set(
|
|
293
|
-
|
|
318
|
+
mcpClientStateKey(
|
|
319
|
+
state.connectionId,
|
|
320
|
+
state.eventName,
|
|
321
|
+
state.arguments,
|
|
322
|
+
),
|
|
294
323
|
state,
|
|
295
324
|
);
|
|
296
325
|
}
|
|
@@ -776,13 +805,19 @@ export class PersistentEventStore {
|
|
|
776
805
|
.filter((record) => !matchId || record.matchId === matchId);
|
|
777
806
|
}
|
|
778
807
|
|
|
779
|
-
async appendMcpOccurrence(serverId, eventInput) {
|
|
808
|
+
async appendMcpOccurrence(serverId, eventInput, subscriptionIdInput = null) {
|
|
780
809
|
return this.#serialized(async () => {
|
|
781
810
|
const event = McpEventOccurrenceSchema.parse(eventInput);
|
|
782
|
-
const
|
|
811
|
+
const subscriptionId =
|
|
812
|
+
subscriptionIdInput === null || subscriptionIdInput === undefined
|
|
813
|
+
? null
|
|
814
|
+
: String(subscriptionIdInput);
|
|
815
|
+
const key =
|
|
816
|
+
`${serverId}:${subscriptionId ?? ''}:${event.eventId}`;
|
|
783
817
|
const existing = this.#mcpOccurrences.find(
|
|
784
818
|
(record) =>
|
|
785
819
|
record.serverId === serverId &&
|
|
820
|
+
(record.subscriptionId ?? null) === subscriptionId &&
|
|
786
821
|
record.event.eventId === event.eventId,
|
|
787
822
|
);
|
|
788
823
|
if (this.#mcpOccurrenceKeys.has(key)) {
|
|
@@ -794,7 +829,12 @@ export class PersistentEventStore {
|
|
|
794
829
|
}
|
|
795
830
|
|
|
796
831
|
const sequence = this.latestMcpEventSequence() + 1;
|
|
797
|
-
const record = {
|
|
832
|
+
const record = {
|
|
833
|
+
sequence,
|
|
834
|
+
serverId,
|
|
835
|
+
subscriptionId,
|
|
836
|
+
event,
|
|
837
|
+
};
|
|
798
838
|
this.#mcpOccurrenceKeys.add(key);
|
|
799
839
|
this.#mcpOccurrences.push(record);
|
|
800
840
|
await appendFile(
|
|
@@ -816,6 +856,7 @@ export class PersistentEventStore {
|
|
|
816
856
|
.map((record) => ({
|
|
817
857
|
sequence: record.sequence,
|
|
818
858
|
serverId: record.serverId,
|
|
859
|
+
subscriptionId: record.subscriptionId ?? null,
|
|
819
860
|
event: {
|
|
820
861
|
...record.event,
|
|
821
862
|
data: { ...record.event.data },
|
|
@@ -823,9 +864,9 @@ export class PersistentEventStore {
|
|
|
823
864
|
}));
|
|
824
865
|
}
|
|
825
866
|
|
|
826
|
-
getMcpClientState(connectionId, eventName) {
|
|
867
|
+
getMcpClientState(connectionId, eventName, args = {}) {
|
|
827
868
|
return this.#mcpClientStates.get(
|
|
828
|
-
|
|
869
|
+
mcpClientStateKey(connectionId, eventName, args),
|
|
829
870
|
) ?? null;
|
|
830
871
|
}
|
|
831
872
|
|
|
@@ -843,24 +884,42 @@ export class PersistentEventStore {
|
|
|
843
884
|
if (!connectionId || !eventName) {
|
|
844
885
|
throw new Error('MCP client state requires connectionId and eventName');
|
|
845
886
|
}
|
|
846
|
-
const
|
|
887
|
+
const args =
|
|
888
|
+
input.arguments && !Array.isArray(input.arguments) && typeof input.arguments === 'object'
|
|
889
|
+
? input.arguments
|
|
890
|
+
: {};
|
|
891
|
+
const previous = this.getMcpClientState(connectionId, eventName, args);
|
|
847
892
|
const state = {
|
|
848
893
|
connectionId,
|
|
849
894
|
serverId: String(input.serverId || previous?.serverId || connectionId),
|
|
850
895
|
eventName,
|
|
896
|
+
arguments: args,
|
|
897
|
+
subscriptionId:
|
|
898
|
+
input.subscriptionId
|
|
899
|
+
? String(input.subscriptionId)
|
|
900
|
+
: previous?.subscriptionId ?? null,
|
|
901
|
+
deliveryMode: String(input.deliveryMode || previous?.deliveryMode || 'poll'),
|
|
851
902
|
cursor: input.cursor === null || input.cursor === undefined
|
|
852
903
|
? null
|
|
853
904
|
: String(input.cursor),
|
|
854
905
|
updatedAt: new Date().toISOString(),
|
|
906
|
+
...(input.nextPollAt || previous?.nextPollAt
|
|
907
|
+
? { nextPollAt: String(input.nextPollAt || previous.nextPollAt) }
|
|
908
|
+
: {}),
|
|
855
909
|
...(input.lastEventAt || previous?.lastEventAt
|
|
856
910
|
? { lastEventAt: String(input.lastEventAt || previous.lastEventAt) }
|
|
857
911
|
: {}),
|
|
858
912
|
...(input.lastError
|
|
859
913
|
? { lastError: String(input.lastError) }
|
|
860
914
|
: {}),
|
|
915
|
+
...(typeof input.truncated === 'boolean'
|
|
916
|
+
? { truncated: input.truncated }
|
|
917
|
+
: typeof previous?.truncated === 'boolean'
|
|
918
|
+
? { truncated: previous.truncated }
|
|
919
|
+
: {}),
|
|
861
920
|
};
|
|
862
921
|
this.#mcpClientStates.set(
|
|
863
|
-
|
|
922
|
+
mcpClientStateKey(connectionId, eventName, args),
|
|
864
923
|
state,
|
|
865
924
|
);
|
|
866
925
|
await appendFile(
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
DerivedContractRegistry,
|
|
7
7
|
} from './derived-contract-registry.mjs';
|
|
8
|
+
import { assertJsonSchemaValue } from './json-schema.mjs';
|
|
8
9
|
|
|
9
10
|
function actorIdentity(actor) {
|
|
10
11
|
if (!actor || typeof actor !== 'object') {
|
|
@@ -66,6 +67,67 @@ function scalarSchemaForConstant(value) {
|
|
|
66
67
|
return {};
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
function walkPatternValue(value, visitor) {
|
|
71
|
+
if (!value || typeof value !== 'object') return;
|
|
72
|
+
if (value.kind === 'field') {
|
|
73
|
+
visitor(value.ref, value.path, 'pattern value');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (value.kind === 'arithmetic') {
|
|
77
|
+
for (const arg of value.args ?? []) walkPatternValue(arg, visitor);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function walkPatternNode(node, visitor) {
|
|
82
|
+
if (!node || typeof node !== 'object') return;
|
|
83
|
+
|
|
84
|
+
if (node.kind === 'compare') {
|
|
85
|
+
walkPatternValue(node.left, visitor);
|
|
86
|
+
if (node.right) walkPatternValue(node.right, visitor);
|
|
87
|
+
}
|
|
88
|
+
if (
|
|
89
|
+
node.kind === 'aggregate' ||
|
|
90
|
+
node.kind === 'state' ||
|
|
91
|
+
node.kind === 'distinct'
|
|
92
|
+
) {
|
|
93
|
+
if (node.path) visitor(node.ref, node.path, `pattern ${node.kind}`);
|
|
94
|
+
}
|
|
95
|
+
if (node.kind === 'semantic') {
|
|
96
|
+
for (const input of node.input ?? []) {
|
|
97
|
+
const [ref, ...parts] = String(input).split('.');
|
|
98
|
+
if (ref && parts.length) {
|
|
99
|
+
visitor(ref, parts.join('.'), 'pattern semantic input');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (Array.isArray(node.children)) {
|
|
105
|
+
for (const child of node.children) walkPatternNode(child, visitor);
|
|
106
|
+
}
|
|
107
|
+
if (node.child) walkPatternNode(node.child, visitor);
|
|
108
|
+
if (node.forbidden) walkPatternNode(node.forbidden, visitor);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function assertSubscriptionArguments(source, args, purpose) {
|
|
112
|
+
const inputSchema = source?.inputSchema;
|
|
113
|
+
if (
|
|
114
|
+
!inputSchema ||
|
|
115
|
+
typeof inputSchema !== 'object' ||
|
|
116
|
+
Object.keys(inputSchema).length === 0
|
|
117
|
+
) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
assertJsonSchemaValue(inputSchema, args ?? {});
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const wrapped = new Error(
|
|
124
|
+
`${purpose} arguments are invalid for ${source.serverId}/${source.eventName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
125
|
+
);
|
|
126
|
+
wrapped.code = 'TRIGGER_SOURCE_ARGUMENTS_INVALID';
|
|
127
|
+
throw wrapped;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
69
131
|
function assertAdvertisedPath(source, path, purpose) {
|
|
70
132
|
const payloadSchema = source?.payloadSchema;
|
|
71
133
|
if (
|
|
@@ -695,6 +757,11 @@ export class TriggerControlPlane {
|
|
|
695
757
|
|
|
696
758
|
for (const clause of definition.clauses) {
|
|
697
759
|
const source = resolveSource(clause);
|
|
760
|
+
assertSubscriptionArguments(
|
|
761
|
+
source,
|
|
762
|
+
clause.arguments ?? {},
|
|
763
|
+
`Trigger clause ${clause.id}`,
|
|
764
|
+
);
|
|
698
765
|
for (const predicate of clause.where ?? []) {
|
|
699
766
|
assertAdvertisedPath(
|
|
700
767
|
source,
|
|
@@ -716,6 +783,59 @@ export class TriggerControlPlane {
|
|
|
716
783
|
);
|
|
717
784
|
}
|
|
718
785
|
|
|
786
|
+
if (definition.patternV2) {
|
|
787
|
+
const clauseForRef = (ref) =>
|
|
788
|
+
definition.clauses.find((candidate) => candidate.id === ref);
|
|
789
|
+
const sourceForRef = (ref) => {
|
|
790
|
+
const clause = clauseForRef(ref);
|
|
791
|
+
return clause ? resolveSource(clause) : null;
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
for (const dimension of definition.patternV2.partitionBy ?? []) {
|
|
795
|
+
for (const field of dimension.fields ?? []) {
|
|
796
|
+
assertAdvertisedPath(
|
|
797
|
+
sourceForRef(field.ref),
|
|
798
|
+
field.path,
|
|
799
|
+
`Pattern partition ${dimension.key}`,
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
walkPatternNode(definition.patternV2.root, (ref, fieldPath, purpose) => {
|
|
805
|
+
assertAdvertisedPath(
|
|
806
|
+
sourceForRef(ref),
|
|
807
|
+
fieldPath,
|
|
808
|
+
purpose,
|
|
809
|
+
);
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
for (const measure of definition.derivedEvent?.measures ?? []) {
|
|
814
|
+
const expression = measure.expression;
|
|
815
|
+
const clauseForRef = (ref) =>
|
|
816
|
+
definition.clauses.find((candidate) => candidate.id === ref);
|
|
817
|
+
const sourceForRef = (ref) => {
|
|
818
|
+
const clause = clauseForRef(ref);
|
|
819
|
+
return clause ? resolveSource(clause) : null;
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
if (expression.kind === 'aggregate' && expression.path) {
|
|
823
|
+
assertAdvertisedPath(
|
|
824
|
+
sourceForRef(expression.ref),
|
|
825
|
+
expression.path,
|
|
826
|
+
`Derived measure ${measure.key}`,
|
|
827
|
+
);
|
|
828
|
+
} else {
|
|
829
|
+
walkPatternValue(expression, (ref, fieldPath) => {
|
|
830
|
+
assertAdvertisedPath(
|
|
831
|
+
sourceForRef(ref),
|
|
832
|
+
fieldPath,
|
|
833
|
+
`Derived measure ${measure.key}`,
|
|
834
|
+
);
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
719
839
|
for (const projection of definition.derivedEvent?.projections ?? []) {
|
|
720
840
|
const clause = definition.clauses.find(
|
|
721
841
|
(candidate) => candidate.id === projection.ref,
|
|
@@ -780,6 +900,65 @@ export class TriggerControlPlane {
|
|
|
780
900
|
}
|
|
781
901
|
|
|
782
902
|
const allSources = this.store.listEventSources();
|
|
903
|
+
const sourceSchemaForRefPath = (ref, fieldPath) => {
|
|
904
|
+
const clause = definition.clauses.find(
|
|
905
|
+
(candidate) => candidate.id === ref,
|
|
906
|
+
);
|
|
907
|
+
const source = clause
|
|
908
|
+
? allSources.find(
|
|
909
|
+
(candidate) =>
|
|
910
|
+
candidate.eventName === clause.event &&
|
|
911
|
+
candidate.serverId === clause.serverId &&
|
|
912
|
+
(
|
|
913
|
+
!clause.contractVersion ||
|
|
914
|
+
String(candidate.metadata?.contractVersion || '') ===
|
|
915
|
+
String(clause.contractVersion)
|
|
916
|
+
),
|
|
917
|
+
)
|
|
918
|
+
: null;
|
|
919
|
+
return getSchemaNode(source?.payloadSchema, fieldPath) ?? {};
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
const schemaForMeasureExpression = (expression) => {
|
|
923
|
+
if (!expression || typeof expression !== 'object') return {};
|
|
924
|
+
if (expression.kind === 'literal') {
|
|
925
|
+
if (expression.value === null) return { type: 'null' };
|
|
926
|
+
return scalarSchemaForConstant(expression.value);
|
|
927
|
+
}
|
|
928
|
+
if (expression.kind === 'field') {
|
|
929
|
+
return sourceSchemaForRefPath(expression.ref, expression.path);
|
|
930
|
+
}
|
|
931
|
+
if (
|
|
932
|
+
expression.kind === 'occurredAt' ||
|
|
933
|
+
expression.kind === 'arithmetic'
|
|
934
|
+
) {
|
|
935
|
+
return { type: 'number' };
|
|
936
|
+
}
|
|
937
|
+
if (expression.kind === 'aggregate') {
|
|
938
|
+
if (
|
|
939
|
+
['count', 'countDistinct'].includes(expression.function)
|
|
940
|
+
) {
|
|
941
|
+
return { type: 'integer' };
|
|
942
|
+
}
|
|
943
|
+
if (
|
|
944
|
+
['sum', 'avg', 'min', 'max', 'stddev', 'percentile']
|
|
945
|
+
.includes(expression.function)
|
|
946
|
+
) {
|
|
947
|
+
return { type: 'number' };
|
|
948
|
+
}
|
|
949
|
+
return sourceSchemaForRefPath(
|
|
950
|
+
expression.ref,
|
|
951
|
+
expression.path,
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
return {};
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
for (const measure of definition.derivedEvent.measures ?? []) {
|
|
958
|
+
properties[measure.key] =
|
|
959
|
+
schemaForMeasureExpression(measure.expression);
|
|
960
|
+
}
|
|
961
|
+
|
|
783
962
|
for (const projection of definition.derivedEvent.projections ?? []) {
|
|
784
963
|
const clause = definition.clauses.find(
|
|
785
964
|
(candidate) => candidate.id === projection.ref,
|
|
@@ -806,6 +985,7 @@ export class TriggerControlPlane {
|
|
|
806
985
|
required: [
|
|
807
986
|
'_derived',
|
|
808
987
|
...Object.keys(definition.derivedEvent.constants ?? {}),
|
|
988
|
+
...(definition.derivedEvent.measures ?? []).map((measure) => measure.key),
|
|
809
989
|
],
|
|
810
990
|
};
|
|
811
991
|
}
|
|
@@ -166,6 +166,18 @@ function explain({
|
|
|
166
166
|
};
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
if (definition.patternV2) {
|
|
170
|
+
return {
|
|
171
|
+
code: 'pattern_v2_waiting',
|
|
172
|
+
summary:
|
|
173
|
+
'Pattern AST v2 has buffered evidence but no new complete match is currently eligible.',
|
|
174
|
+
patternVersion: '2',
|
|
175
|
+
bufferRole: match.patternState?.role ?? null,
|
|
176
|
+
semanticDecisions:
|
|
177
|
+
match.patternState?.semanticDecisions ?? [],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
169
181
|
const missing = clauses
|
|
170
182
|
.filter((clause) => clause.status !== 'satisfied')
|
|
171
183
|
.map((clause) => clause.clauseId);
|
|
@@ -312,6 +324,7 @@ export class TriggerInspector {
|
|
|
312
324
|
derivedEvent: definition.derivedEvent ?? null,
|
|
313
325
|
expression: definition.expression,
|
|
314
326
|
temporal: definition.temporal ?? [],
|
|
327
|
+
patternV2: definition.patternV2 ?? null,
|
|
315
328
|
lifecycle: definition.lifecycle ?? {},
|
|
316
329
|
},
|
|
317
330
|
lifecycle: state,
|
|
@@ -323,6 +336,7 @@ export class TriggerInspector {
|
|
|
323
336
|
openedAt: match.openedAt,
|
|
324
337
|
expiresAt: match.expiresAt,
|
|
325
338
|
updatedAt: match.updatedAt,
|
|
339
|
+
patternState: match.patternState ?? null,
|
|
326
340
|
}
|
|
327
341
|
: null,
|
|
328
342
|
clauses,
|
|
@@ -360,6 +374,7 @@ export class TriggerInspector {
|
|
|
360
374
|
rootEvidence: record.rootEvidence,
|
|
361
375
|
})),
|
|
362
376
|
matchHistory,
|
|
377
|
+
patternState: match?.patternState ?? null,
|
|
363
378
|
},
|
|
364
379
|
why: explain({
|
|
365
380
|
definition,
|