@saptools/service-flow 0.1.70 → 0.1.72
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 +13 -0
- package/README.md +9 -5
- package/TECHNICAL-NOTE.md +13 -0
- package/dist/{chunk-GSLFY6J2.js → chunk-Z6D433R5.js} +8696 -6476
- package/dist/chunk-Z6D433R5.js.map +1 -0
- package/dist/cli.js +685 -142
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +53 -14
- package/dist/index.js +2 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/doctor-event-quality.ts +371 -0
- package/src/cli/doctor.ts +4 -6
- package/src/cli.ts +1 -1
- package/src/db/call-fact-repository.ts +6 -3
- package/src/db/current-fact-semantics.ts +5 -5
- package/src/db/event-fact-semantics.ts +347 -0
- package/src/db/event-surface-invalidation.ts +38 -0
- package/src/db/fact-json-inventory.ts +16 -0
- package/src/db/migrations.ts +12 -1
- package/src/db/package-target-invalidation.ts +79 -0
- package/src/db/repositories.ts +28 -2
- package/src/db/schema-structure.ts +41 -1
- package/src/db/schema.ts +6 -2
- package/src/indexer/repository-indexer.ts +45 -6
- package/src/linker/cross-repo-linker.ts +25 -3
- package/src/linker/event-environment-link.ts +211 -0
- package/src/linker/event-shape-candidate-linker.ts +161 -0
- package/src/linker/event-subscription-handler-linker.ts +123 -19
- package/src/linker/event-template-link.ts +40 -6
- package/src/linker/package-event-constant-resolver.ts +298 -0
- package/src/output/table-output.ts +13 -1
- package/src/parsers/decorator-parser.ts +9 -53
- package/src/parsers/environment-declarations.ts +327 -0
- package/src/parsers/event-call-analysis.ts +242 -0
- package/src/parsers/event-environment-reference.ts +231 -0
- package/src/parsers/event-loop-registration.ts +132 -0
- package/src/parsers/event-name-import-resolution.ts +243 -0
- package/src/parsers/event-receiver-analysis.ts +404 -0
- package/src/parsers/event-subscription-facts.ts +4 -0
- package/src/parsers/generated-constants-parser.ts +80 -14
- package/src/parsers/outbound-call-classifier.ts +27 -124
- package/src/parsers/outbound-call-parser.ts +13 -1
- package/src/parsers/outbound-expression-analysis.ts +2 -2
- package/src/parsers/stable-local-value.ts +30 -9
- package/src/parsers/string-constant-lookups.ts +358 -0
- package/src/trace/event-runtime-resolution.ts +24 -3
- package/src/trace/event-shape-candidate-trace.ts +172 -0
- package/src/trace/event-subscriber-traversal.ts +19 -7
- package/src/trace/evidence.ts +7 -0
- package/src/trace/trace-scope-execution.ts +21 -29
- package/src/types.ts +17 -1
- package/src/utils/event-skeleton.ts +207 -0
- package/src/version.ts +1 -1
- package/dist/chunk-GSLFY6J2.js.map +0 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { Db } from '../db/connection.js';
|
|
2
|
+
import { parseEventSkeletonFact } from '../utils/event-skeleton.js';
|
|
3
|
+
|
|
4
|
+
export interface EventShapeCandidateLinkSummary {
|
|
5
|
+
edgeCount: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface EventShapeRow extends Record<string, unknown> {
|
|
9
|
+
id: number;
|
|
10
|
+
repoId: number;
|
|
11
|
+
repoName: string;
|
|
12
|
+
signature: string;
|
|
13
|
+
skeletonJson: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface SubscriberAssociation extends Record<string, unknown> {
|
|
17
|
+
graphEdgeId: number;
|
|
18
|
+
subscribeCallId: number;
|
|
19
|
+
targetKind: string;
|
|
20
|
+
targetId: string;
|
|
21
|
+
status: string;
|
|
22
|
+
evidenceJson: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function eventRows(
|
|
26
|
+
db: Db,
|
|
27
|
+
workspaceId: number,
|
|
28
|
+
callType: 'async_emit' | 'async_subscribe',
|
|
29
|
+
): EventShapeRow[] {
|
|
30
|
+
return db.prepare(`SELECT c.id,c.repo_id repoId,r.name repoName,
|
|
31
|
+
c.event_skeleton_signature signature,
|
|
32
|
+
c.event_skeleton_json skeletonJson
|
|
33
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
34
|
+
WHERE r.workspace_id=? AND c.call_type=?
|
|
35
|
+
AND c.event_skeleton_signature IS NOT NULL
|
|
36
|
+
AND c.event_skeleton_json IS NOT NULL
|
|
37
|
+
ORDER BY c.event_skeleton_signature COLLATE BINARY,
|
|
38
|
+
r.name COLLATE BINARY,r.id,c.source_file COLLATE BINARY,
|
|
39
|
+
c.call_site_start_offset,c.call_site_end_offset,c.id`).all(
|
|
40
|
+
workspaceId, callType,
|
|
41
|
+
) as unknown as EventShapeRow[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function associations(
|
|
45
|
+
db: Db,
|
|
46
|
+
workspaceId: number,
|
|
47
|
+
generation: number,
|
|
48
|
+
): SubscriberAssociation[] {
|
|
49
|
+
return db.prepare(`SELECT id graphEdgeId,
|
|
50
|
+
CAST(json_extract(evidence_json,'$.subscribeCallId') AS INTEGER)
|
|
51
|
+
subscribeCallId,
|
|
52
|
+
to_kind targetKind,to_id targetId,status,evidence_json evidenceJson
|
|
53
|
+
FROM graph_edges
|
|
54
|
+
WHERE workspace_id=? AND generation=?
|
|
55
|
+
AND edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
56
|
+
AND to_kind='symbol'
|
|
57
|
+
ORDER BY subscribeCallId,id`).all(
|
|
58
|
+
workspaceId, generation,
|
|
59
|
+
) as unknown as SubscriberAssociation[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parsedEvidence(value: string): Record<string, unknown> {
|
|
63
|
+
try {
|
|
64
|
+
const parsed: unknown = JSON.parse(value);
|
|
65
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
66
|
+
? parsed as Record<string, unknown> : {};
|
|
67
|
+
} catch {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function candidateEligible(
|
|
73
|
+
emit: EventShapeRow,
|
|
74
|
+
subscribe: EventShapeRow,
|
|
75
|
+
): boolean {
|
|
76
|
+
if (emit.signature !== subscribe.signature) return false;
|
|
77
|
+
const left = parseEventSkeletonFact(emit.skeletonJson);
|
|
78
|
+
const right = parseEventSkeletonFact(subscribe.skeletonJson);
|
|
79
|
+
return Boolean(left?.candidateEligible && right?.candidateEligible
|
|
80
|
+
&& left.holeCount === right.holeCount
|
|
81
|
+
&& JSON.stringify(left.literalSpans) === JSON.stringify(right.literalSpans));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function candidateEvidence(
|
|
85
|
+
emit: EventShapeRow,
|
|
86
|
+
subscribe: EventShapeRow,
|
|
87
|
+
association: SubscriberAssociation,
|
|
88
|
+
): Record<string, unknown> {
|
|
89
|
+
const evidence = parsedEvidence(association.evidenceJson);
|
|
90
|
+
return {
|
|
91
|
+
publishCallId: emit.id,
|
|
92
|
+
subscribeCallId: subscribe.id,
|
|
93
|
+
eventSkeletonSignature: emit.signature,
|
|
94
|
+
dispatchScope: 'workspace_event_name_only',
|
|
95
|
+
dispatchCertainty: 'skeleton_equivalent',
|
|
96
|
+
subscriptionRepositoryId: subscribe.repoId,
|
|
97
|
+
subscriptionRepositoryName: subscribe.repoName,
|
|
98
|
+
subscriptionConsumerRepositoryId:
|
|
99
|
+
evidence.subscriptionConsumerRepositoryId,
|
|
100
|
+
subscriptionConsumerRepositoryName:
|
|
101
|
+
evidence.subscriptionConsumerRepositoryName,
|
|
102
|
+
handlerSymbolId: Number(association.targetId),
|
|
103
|
+
associationGraphEdgeId: association.graphEdgeId,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function insertCandidate(
|
|
108
|
+
db: Db,
|
|
109
|
+
workspaceId: number,
|
|
110
|
+
generation: number,
|
|
111
|
+
emit: EventShapeRow,
|
|
112
|
+
subscribe: EventShapeRow,
|
|
113
|
+
association: SubscriberAssociation,
|
|
114
|
+
): void {
|
|
115
|
+
db.prepare(`INSERT INTO graph_edges(
|
|
116
|
+
workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,
|
|
117
|
+
confidence,evidence_json,is_dynamic,unresolved_reason,generation
|
|
118
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`).run(
|
|
119
|
+
workspaceId,
|
|
120
|
+
'EVENT_SHAPE_CANDIDATE_SUBSCRIBER',
|
|
121
|
+
'dynamic',
|
|
122
|
+
'call',
|
|
123
|
+
String(emit.id),
|
|
124
|
+
association.targetKind,
|
|
125
|
+
association.targetId,
|
|
126
|
+
0.3,
|
|
127
|
+
JSON.stringify(candidateEvidence(
|
|
128
|
+
emit, subscribe, association,
|
|
129
|
+
)),
|
|
130
|
+
1,
|
|
131
|
+
'event_skeleton_equivalent_non_authoritative',
|
|
132
|
+
generation,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function linkEventShapeCandidates(
|
|
137
|
+
db: Db,
|
|
138
|
+
workspaceId: number,
|
|
139
|
+
generation: number,
|
|
140
|
+
): EventShapeCandidateLinkSummary {
|
|
141
|
+
const emits = eventRows(db, workspaceId, 'async_emit');
|
|
142
|
+
const subscriptions = eventRows(db, workspaceId, 'async_subscribe');
|
|
143
|
+
const bySubscription = new Map<number, SubscriberAssociation[]>();
|
|
144
|
+
for (const association of associations(db, workspaceId, generation))
|
|
145
|
+
bySubscription.set(association.subscribeCallId, [
|
|
146
|
+
...(bySubscription.get(association.subscribeCallId) ?? []),
|
|
147
|
+
association,
|
|
148
|
+
]);
|
|
149
|
+
let edgeCount = 0;
|
|
150
|
+
for (const emit of emits)
|
|
151
|
+
for (const subscription of subscriptions) {
|
|
152
|
+
if (!candidateEligible(emit, subscription)) continue;
|
|
153
|
+
for (const association of bySubscription.get(subscription.id) ?? []) {
|
|
154
|
+
insertCandidate(
|
|
155
|
+
db, workspaceId, generation, emit, subscription, association,
|
|
156
|
+
);
|
|
157
|
+
edgeCount += 1;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { edgeCount };
|
|
161
|
+
}
|
|
@@ -3,6 +3,11 @@ import {
|
|
|
3
3
|
linkEventTemplate,
|
|
4
4
|
type LinkedEventTemplate,
|
|
5
5
|
} from './event-template-link.js';
|
|
6
|
+
import {
|
|
7
|
+
subscriptionEnvironmentTargets,
|
|
8
|
+
type SubscriptionEnvironmentTarget,
|
|
9
|
+
} from './event-environment-link.js';
|
|
10
|
+
import { parseEventSkeletonFact } from '../utils/event-skeleton.js';
|
|
6
11
|
|
|
7
12
|
export interface SubscriptionHandlerLinkSummary {
|
|
8
13
|
edgeCount: number;
|
|
@@ -25,6 +30,9 @@ interface SubscriptionRow {
|
|
|
25
30
|
endOffset?: number | null;
|
|
26
31
|
confidence: number;
|
|
27
32
|
unresolvedReason?: string | null;
|
|
33
|
+
packageName?: string | null;
|
|
34
|
+
environmentJson?: string | null;
|
|
35
|
+
eventSkeletonJson?: string | null;
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
interface HandlerCallRow {
|
|
@@ -63,7 +71,10 @@ function subscriptionRows(db: Db, workspaceId: number): SubscriptionRow[] {
|
|
|
63
71
|
c.source_symbol_id sourceSymbolId,c.event_name_expr eventName,
|
|
64
72
|
c.source_file sourceFile,c.source_line sourceLine,
|
|
65
73
|
c.call_site_start_offset startOffset,c.call_site_end_offset endOffset,
|
|
66
|
-
c.confidence,c.unresolved_reason unresolvedReason
|
|
74
|
+
c.confidence,c.unresolved_reason unresolvedReason,
|
|
75
|
+
c.event_skeleton_json eventSkeletonJson,
|
|
76
|
+
r.package_name packageName,
|
|
77
|
+
r.environment_declarations_json environmentJson
|
|
67
78
|
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
68
79
|
WHERE r.workspace_id=? AND c.call_type='async_subscribe'
|
|
69
80
|
AND json_extract(c.evidence_json,'$.handlerReferenceStatus')
|
|
@@ -208,11 +219,18 @@ function evidenceFor(
|
|
|
208
219
|
subscription: SubscriptionRow,
|
|
209
220
|
association: HandlerAssociation,
|
|
210
221
|
event: LinkedEventTemplate,
|
|
222
|
+
environment?: SubscriptionEnvironmentTarget,
|
|
211
223
|
): Record<string, unknown> {
|
|
212
224
|
const call: Partial<HandlerCallRow> = association.call ?? {};
|
|
213
225
|
const symbolCallReason = boundedSymbolCallReason(call.unresolvedReason);
|
|
226
|
+
const environmentAmbiguous = environment?.resolution.status === 'ambiguous'
|
|
227
|
+
|| Number(environment?.collisionCount ?? 1) > 1;
|
|
228
|
+
const resolutionStatus = event.isDynamic
|
|
229
|
+
? 'unresolved'
|
|
230
|
+
: environmentAmbiguous ? 'ambiguous' : association.status;
|
|
214
231
|
return {
|
|
215
232
|
eventName: subscription.eventName,
|
|
233
|
+
...(eventSkeletonEvidence(subscription.eventSkeletonJson)),
|
|
216
234
|
...(event.substitution.placeholders.length > 0 ? {
|
|
217
235
|
effectiveEventName: event.targetId,
|
|
218
236
|
eventTemplateResolution: event.substitution,
|
|
@@ -226,6 +244,18 @@ function evidenceFor(
|
|
|
226
244
|
factOrigin: association.factOrigin ?? call.factOrigin,
|
|
227
245
|
repositoryId: subscription.repoId,
|
|
228
246
|
repositoryName: subscription.repoName,
|
|
247
|
+
subscriptionConsumerRepositoryId: environment?.consumerRepoId,
|
|
248
|
+
subscriptionConsumerRepositoryName: environment?.consumerRepoName,
|
|
249
|
+
dispatchCertainty: environment?.resolution.status === 'resolved'
|
|
250
|
+
? 'environment_declaration_exact' : 'static_name_only',
|
|
251
|
+
...(environment?.resolution.status === 'not_applicable' ? {} : {
|
|
252
|
+
eventEnvironmentResolution: {
|
|
253
|
+
status: environment?.resolution.status,
|
|
254
|
+
reason: environment?.resolution.reason,
|
|
255
|
+
provenance: environment?.resolution.provenance,
|
|
256
|
+
collisionCount: environment?.collisionCount,
|
|
257
|
+
},
|
|
258
|
+
}),
|
|
229
259
|
sourceFile: subscription.sourceFile,
|
|
230
260
|
sourceLine: subscription.sourceLine,
|
|
231
261
|
callSiteStartOffset: subscription.startOffset,
|
|
@@ -237,14 +267,23 @@ function evidenceFor(
|
|
|
237
267
|
associationStatus: association.status,
|
|
238
268
|
symbolCallResolutionStatus:
|
|
239
269
|
association.symbolCallResolutionStatus ?? call.status,
|
|
240
|
-
resolutionStatus
|
|
270
|
+
resolutionStatus,
|
|
241
271
|
resolutionStrategy: call.strategy,
|
|
242
272
|
candidateCount: call.candidateCount,
|
|
243
|
-
reasonCode:
|
|
273
|
+
reasonCode: environmentAmbiguous
|
|
274
|
+
? environment?.resolution.reason ?? 'event_environment_value_collision'
|
|
275
|
+
: association.reasonCode,
|
|
244
276
|
...symbolCallReason,
|
|
245
277
|
};
|
|
246
278
|
}
|
|
247
279
|
|
|
280
|
+
function eventSkeletonEvidence(
|
|
281
|
+
value: string | null | undefined,
|
|
282
|
+
): Record<string, unknown> {
|
|
283
|
+
const skeleton = parseEventSkeletonFact(value);
|
|
284
|
+
return skeleton ? { eventSkeleton: skeleton } : {};
|
|
285
|
+
}
|
|
286
|
+
|
|
248
287
|
function boundedSymbolCallReason(
|
|
249
288
|
reason: string | null | undefined,
|
|
250
289
|
): Record<string, unknown> {
|
|
@@ -264,10 +303,20 @@ function insertAssociationEdge(
|
|
|
264
303
|
subscription: SubscriptionRow,
|
|
265
304
|
association: HandlerAssociation,
|
|
266
305
|
event: LinkedEventTemplate,
|
|
306
|
+
environment?: SubscriptionEnvironmentTarget,
|
|
267
307
|
): void {
|
|
268
|
-
const
|
|
308
|
+
const environmentAmbiguous = environment?.resolution.status === 'ambiguous'
|
|
309
|
+
|| Number(environment?.collisionCount ?? 1) > 1;
|
|
310
|
+
const status = event.isDynamic
|
|
311
|
+
? 'unresolved'
|
|
312
|
+
: environmentAmbiguous ? 'ambiguous' : association.status;
|
|
269
313
|
const reason = event.isDynamic
|
|
270
|
-
?
|
|
314
|
+
? event.substitution.missing.length > 0
|
|
315
|
+
? 'event_template_variables_missing'
|
|
316
|
+
: event.unresolvedReason ?? 'event_name_unsupported_constant_expression'
|
|
317
|
+
: environmentAmbiguous
|
|
318
|
+
? environment?.resolution.reason
|
|
319
|
+
?? 'event_environment_value_collision'
|
|
271
320
|
: association.reasonCode ?? association.call?.unresolvedReason ?? null;
|
|
272
321
|
db.prepare(`INSERT INTO graph_edges(
|
|
273
322
|
workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,
|
|
@@ -281,13 +330,68 @@ function insertAssociationEdge(
|
|
|
281
330
|
association.toKind,
|
|
282
331
|
association.toId,
|
|
283
332
|
association.call?.confidence ?? subscription.confidence,
|
|
284
|
-
JSON.stringify(evidenceFor(
|
|
333
|
+
JSON.stringify(evidenceFor(
|
|
334
|
+
subscription, association, event, environment,
|
|
335
|
+
)),
|
|
285
336
|
event.isDynamic ? 1 : 0,
|
|
286
337
|
reason,
|
|
287
338
|
generation,
|
|
288
339
|
);
|
|
289
340
|
}
|
|
290
341
|
|
|
342
|
+
function linkedSubscriptionEvents(
|
|
343
|
+
db: Db,
|
|
344
|
+
workspaceId: number,
|
|
345
|
+
subscription: SubscriptionRow,
|
|
346
|
+
variables: Record<string, string>,
|
|
347
|
+
): Array<{
|
|
348
|
+
event: LinkedEventTemplate;
|
|
349
|
+
environment: SubscriptionEnvironmentTarget;
|
|
350
|
+
}> {
|
|
351
|
+
const skeleton = parseEventSkeletonFact(subscription.eventSkeletonJson);
|
|
352
|
+
return subscriptionEnvironmentTargets(
|
|
353
|
+
db,
|
|
354
|
+
workspaceId,
|
|
355
|
+
subscription.packageName ?? undefined,
|
|
356
|
+
subscription.eventSkeletonJson,
|
|
357
|
+
subscription.environmentJson,
|
|
358
|
+
variables,
|
|
359
|
+
).map((environment) => ({
|
|
360
|
+
environment,
|
|
361
|
+
event: linkEventTemplate(
|
|
362
|
+
subscription.eventName,
|
|
363
|
+
environment.resolution.variables,
|
|
364
|
+
subscription.unresolvedReason ?? undefined,
|
|
365
|
+
skeleton,
|
|
366
|
+
),
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function targetStatus(
|
|
371
|
+
association: HandlerAssociation,
|
|
372
|
+
event: LinkedEventTemplate,
|
|
373
|
+
environment: SubscriptionEnvironmentTarget,
|
|
374
|
+
): 'resolved' | 'ambiguous' | 'unresolved' {
|
|
375
|
+
if (event.isDynamic || association.status === 'unresolved')
|
|
376
|
+
return 'unresolved';
|
|
377
|
+
if (association.status === 'ambiguous'
|
|
378
|
+
|| environment.resolution.status === 'ambiguous'
|
|
379
|
+
|| environment.collisionCount > 1) return 'ambiguous';
|
|
380
|
+
return 'resolved';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function incrementSummary(
|
|
384
|
+
summary: SubscriptionHandlerLinkSummary,
|
|
385
|
+
status: 'resolved' | 'ambiguous' | 'unresolved',
|
|
386
|
+
missing: boolean,
|
|
387
|
+
): void {
|
|
388
|
+
summary.edgeCount += 1;
|
|
389
|
+
summary.resolvedCount += status === 'resolved' ? 1 : 0;
|
|
390
|
+
summary.ambiguousCount += status === 'ambiguous' ? 1 : 0;
|
|
391
|
+
summary.unresolvedCount += status === 'unresolved' ? 1 : 0;
|
|
392
|
+
summary.missingAssociationCount += missing ? 1 : 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
291
395
|
export function linkEventSubscriptionHandlers(
|
|
292
396
|
db: Db,
|
|
293
397
|
workspaceId: number,
|
|
@@ -302,22 +406,22 @@ export function linkEventSubscriptionHandlers(
|
|
|
302
406
|
missingAssociationCount: 0,
|
|
303
407
|
};
|
|
304
408
|
for (const subscription of subscriptionRows(db, workspaceId)) {
|
|
305
|
-
const event = linkEventTemplate(
|
|
306
|
-
subscription.eventName, variables,
|
|
307
|
-
subscription.unresolvedReason ?? undefined,
|
|
308
|
-
);
|
|
309
409
|
const association = associationFor(
|
|
310
410
|
subscription, roleSiteRows(db, subscription),
|
|
311
411
|
);
|
|
312
|
-
|
|
313
|
-
db, workspaceId,
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
412
|
+
for (const target of linkedSubscriptionEvents(
|
|
413
|
+
db, workspaceId, subscription, variables,
|
|
414
|
+
)) {
|
|
415
|
+
insertAssociationEdge(
|
|
416
|
+
db, workspaceId, generation, subscription, association,
|
|
417
|
+
target.event, target.environment,
|
|
418
|
+
);
|
|
419
|
+
incrementSummary(
|
|
420
|
+
summary,
|
|
421
|
+
targetStatus(association, target.event, target.environment),
|
|
422
|
+
association.missing,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
321
425
|
}
|
|
322
426
|
return summary;
|
|
323
427
|
}
|
|
@@ -3,6 +3,14 @@ import {
|
|
|
3
3
|
substituteVariables,
|
|
4
4
|
type RuntimeSubstitution,
|
|
5
5
|
} from './dynamic-edge-resolver.js';
|
|
6
|
+
import {
|
|
7
|
+
resolveEventEnvironment,
|
|
8
|
+
} from './event-environment-link.js';
|
|
9
|
+
import {
|
|
10
|
+
eventTemplateVariables,
|
|
11
|
+
parseEventSkeletonFact,
|
|
12
|
+
type EventSkeletonFact,
|
|
13
|
+
} from '../utils/event-skeleton.js';
|
|
6
14
|
|
|
7
15
|
export interface LinkedEventTemplate {
|
|
8
16
|
targetId: string;
|
|
@@ -17,8 +25,11 @@ export function linkEventTemplate(
|
|
|
17
25
|
template: string,
|
|
18
26
|
variables: Record<string, string>,
|
|
19
27
|
parserReason?: string,
|
|
28
|
+
skeleton?: EventSkeletonFact,
|
|
20
29
|
): LinkedEventTemplate {
|
|
21
|
-
const substitution = substituteVariables(
|
|
30
|
+
const substitution = substituteVariables(
|
|
31
|
+
template, eventTemplateVariables(skeleton, variables),
|
|
32
|
+
);
|
|
22
33
|
const missing = substitution.missing.length > 0;
|
|
23
34
|
const unsupportedDynamic = substitution.placeholders.length === 0
|
|
24
35
|
&& parserReason !== undefined;
|
|
@@ -47,18 +58,41 @@ export function insertEventCallEdge(
|
|
|
47
58
|
evidence: Record<string, unknown>,
|
|
48
59
|
): { status: string; callType: string } {
|
|
49
60
|
const callType = String(call.call_type);
|
|
61
|
+
const skeleton = parseEventSkeletonFact(call.event_skeleton_json);
|
|
62
|
+
const environment = resolveEventEnvironment(
|
|
63
|
+
call.event_skeleton_json,
|
|
64
|
+
call.environmentDeclarationsJson,
|
|
65
|
+
variables,
|
|
66
|
+
);
|
|
50
67
|
const event = linkEventTemplate(
|
|
51
|
-
String(call.event_name_expr ?? ''), variables,
|
|
68
|
+
String(call.event_name_expr ?? ''), environment.variables,
|
|
52
69
|
typeof call.unresolved_reason === 'string'
|
|
53
70
|
? call.unresolved_reason : undefined,
|
|
71
|
+
skeleton,
|
|
54
72
|
);
|
|
55
73
|
const edgeType = event.isDynamic
|
|
56
74
|
? 'DYNAMIC_EDGE_CANDIDATE'
|
|
57
75
|
: callType === 'async_emit'
|
|
58
76
|
? 'HANDLER_EMITS_EVENT' : 'EVENT_CONSUMED_BY_HANDLER';
|
|
59
|
-
const eventEvidence =
|
|
60
|
-
|
|
61
|
-
:
|
|
77
|
+
const eventEvidence = {
|
|
78
|
+
...evidence,
|
|
79
|
+
dispatchScope: 'workspace_event_name_only',
|
|
80
|
+
dispatchCertainty: environment.status === 'resolved'
|
|
81
|
+
? 'environment_declaration_exact'
|
|
82
|
+
: event.isDynamic ? 'runtime_variables_required' : 'static_name_only',
|
|
83
|
+
...(skeleton ? { eventSkeleton: skeleton } : {}),
|
|
84
|
+
...(event.substitution.placeholders.length > 0
|
|
85
|
+
? { eventTemplateResolution: event.substitution } : {}),
|
|
86
|
+
...(environment.status === 'not_applicable' ? {} : {
|
|
87
|
+
eventEnvironmentResolution: {
|
|
88
|
+
status: environment.status,
|
|
89
|
+
reason: environment.reason,
|
|
90
|
+
provenance: environment.provenance,
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
const unresolvedReason = environment.reason
|
|
95
|
+
?? event.unresolvedReason;
|
|
62
96
|
db.prepare(`INSERT INTO graph_edges(
|
|
63
97
|
workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,
|
|
64
98
|
confidence,evidence_json,is_dynamic,unresolved_reason,generation
|
|
@@ -66,7 +100,7 @@ export function insertEventCallEdge(
|
|
|
66
100
|
workspaceId, edgeType, event.status, 'call', String(call.id),
|
|
67
101
|
event.targetKind, event.targetId, Number(call.confidence ?? 0.2),
|
|
68
102
|
JSON.stringify(eventEvidence), event.isDynamic ? 1 : 0,
|
|
69
|
-
|
|
103
|
+
unresolvedReason ?? null, generation,
|
|
70
104
|
);
|
|
71
105
|
return { status: event.status, callType };
|
|
72
106
|
}
|