@saptools/service-flow 0.1.68 → 0.1.69
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 +10 -0
- package/README.md +11 -5
- package/TECHNICAL-NOTE.md +9 -0
- package/dist/{chunk-AEM4JY22.js → chunk-3N3B5KHV.js} +6983 -5500
- package/dist/chunk-3N3B5KHV.js.map +1 -0
- package/dist/cli.js +317 -105
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/001-index-summary.ts +22 -0
- package/src/cli.ts +151 -87
- package/src/db/000-call-fact-repository.ts +45 -19
- package/src/db/003-current-fact-semantics.ts +1 -0
- package/src/db/004-package-target-invalidation.ts +10 -0
- package/src/db/006-relative-symbol-resolution.ts +28 -7
- package/src/db/008-relative-fact-semantics.ts +6 -3
- package/src/db/009-binding-fact-semantics.ts +5 -0
- package/src/db/012-binding-reference-proof.ts +4 -0
- package/src/db/013-index-publication-failure.ts +91 -0
- package/src/db/014-binding-helper-provenance.ts +17 -0
- package/src/db/repositories.ts +22 -5
- package/src/indexer/cds-extension-resolver.ts +27 -3
- package/src/indexer/repository-indexer.ts +66 -5
- package/src/indexer/workspace-indexer.ts +141 -29
- package/src/linker/004-event-subscription-handler-linker.ts +32 -11
- package/src/linker/006-event-template-link.ts +72 -0
- package/src/linker/007-call-edge-insertion.ts +568 -0
- package/src/linker/cross-repo-linker.ts +2 -165
- package/src/parsers/000-direct-query-execution.ts +11 -0
- package/src/parsers/006-binding-identity.ts +6 -1
- package/src/parsers/014-service-binding-helper-flow.ts +70 -4
- package/src/parsers/021-binding-visibility.ts +17 -1
- package/src/parsers/022-outbound-expression-analysis.ts +700 -0
- package/src/parsers/023-outbound-call-classifier.ts +692 -0
- package/src/parsers/outbound-call-parser.ts +146 -509
- package/src/parsers/symbol-parser.ts +37 -5
- package/src/trace/007-implementation-start-diagnostic.ts +1 -0
- package/src/trace/011-event-subscriber-traversal.ts +100 -8
- package/src/trace/013-trace-root-scopes.ts +2 -3
- package/src/trace/014-compact-contract.ts +6 -0
- package/src/trace/015-trace-edge-recorder.ts +61 -4
- package/src/trace/016-compact-projector.ts +2 -3
- package/src/trace/019-trace-edge-semantics.ts +6 -10
- package/src/trace/020-compact-field-projection.ts +74 -1
- package/src/trace/021-compact-decision-normalization.ts +75 -9
- package/src/trace/024-compact-observation-decision.ts +7 -2
- package/src/trace/026-trace-start-scope.ts +1 -0
- package/src/trace/027-trace-scope-execution.ts +33 -33
- package/src/trace/030-event-runtime-resolution.ts +151 -0
- package/src/trace/031-local-call-expansion.ts +37 -0
- package/src/trace/implementation-hints.ts +9 -6
- package/src/trace/selectors.ts +1 -0
- package/src/types.ts +2 -1
- package/src/version.ts +1 -1
- package/dist/chunk-AEM4JY22.js.map +0 -1
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import type { Db } from '../db/connection.js';
|
|
2
2
|
import { listRepositories, reposByName } from '../db/repositories.js';
|
|
3
3
|
import { errorMessage } from '../utils/diagnostics.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
prepareRepositoryIndex,
|
|
6
|
+
publishOneRepository,
|
|
7
|
+
recordIndexFailure,
|
|
8
|
+
type PreparedRepositoryIndex,
|
|
9
|
+
} from './repository-indexer.js';
|
|
5
10
|
import { materializeCdsExtensionOperations } from './cds-extension-resolver.js';
|
|
6
11
|
import {
|
|
7
12
|
createPackageInvalidationBatch,
|
|
8
13
|
finalizePackageTargetInvalidations,
|
|
14
|
+
mergePackageInvalidationEffects,
|
|
15
|
+
type PackageInvalidationBatch,
|
|
9
16
|
} from '../db/004-package-target-invalidation.js';
|
|
17
|
+
import {
|
|
18
|
+
isPreparedRepositorySnapshotError,
|
|
19
|
+
} from '../db/013-index-publication-failure.js';
|
|
20
|
+
import { binaryCompare } from '../parsers/004-fact-identity.js';
|
|
10
21
|
// Ownerless rows predate PID coordination; this matches doctor's stale-run threshold without taking over a recent legacy writer.
|
|
11
22
|
const LEGACY_OWNER_RECOVERY_MS = 60 * 60 * 1_000;
|
|
12
23
|
type RunningIndexRow = Record<string, unknown>;
|
|
@@ -80,11 +91,21 @@ export function claimIndexRun(
|
|
|
80
91
|
throw error;
|
|
81
92
|
}
|
|
82
93
|
}
|
|
94
|
+
export interface IndexWorkspaceSummary {
|
|
95
|
+
repoCount: number;
|
|
96
|
+
indexedCount: number;
|
|
97
|
+
skippedCount: number;
|
|
98
|
+
failedCount: number;
|
|
99
|
+
failedRepos: Array<{ name: string; code: string }>;
|
|
100
|
+
fileCount: number;
|
|
101
|
+
diagnosticCount: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
83
104
|
export async function indexWorkspace(
|
|
84
105
|
db: Db,
|
|
85
106
|
workspaceId: number,
|
|
86
107
|
options: { repo?: string; force: boolean; injectDerivedMaterializationFailure?: boolean },
|
|
87
|
-
): Promise<
|
|
108
|
+
): Promise<IndexWorkspaceSummary> {
|
|
88
109
|
const repos = selectedRepositories(db, workspaceId, options.repo);
|
|
89
110
|
const runId = claimIndexRun(db, workspaceId, repos.length);
|
|
90
111
|
const state: PreparationState = {
|
|
@@ -92,8 +113,9 @@ export async function indexWorkspace(
|
|
|
92
113
|
};
|
|
93
114
|
try {
|
|
94
115
|
await prepareRepositories(repos, options.force, state);
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
return publishPreparedWorkspaceRows(
|
|
117
|
+
db, workspaceId, runId, state.rows, options,
|
|
118
|
+
);
|
|
97
119
|
} catch (error) {
|
|
98
120
|
finishFailedRun(db, runId, state, error);
|
|
99
121
|
if (state.activeRepoId && state.rows.length < repos.length)
|
|
@@ -111,6 +133,18 @@ interface PreparationState {
|
|
|
111
133
|
activeRepoId?: number;
|
|
112
134
|
}
|
|
113
135
|
|
|
136
|
+
interface PublicationState {
|
|
137
|
+
fileCount: number;
|
|
138
|
+
diagnosticCount: number;
|
|
139
|
+
skippedCount: number;
|
|
140
|
+
indexedCount: number;
|
|
141
|
+
publicationFailureCount: number;
|
|
142
|
+
failedRepoIds: Set<number>;
|
|
143
|
+
failedRepos: Array<{ name: string; code: string }>;
|
|
144
|
+
rows: readonly PreparedRepositoryIndex[];
|
|
145
|
+
activeRepoId?: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
114
148
|
function selectedRepositories(
|
|
115
149
|
db: Db,
|
|
116
150
|
workspaceId: number,
|
|
@@ -145,38 +179,118 @@ async function prepareRepositories(
|
|
|
145
179
|
}
|
|
146
180
|
}
|
|
147
181
|
|
|
148
|
-
function
|
|
182
|
+
export function publishPreparedWorkspaceRows(
|
|
149
183
|
db: Db,
|
|
150
184
|
workspaceId: number,
|
|
151
|
-
options: { injectDerivedMaterializationFailure?: boolean },
|
|
152
185
|
runId: number,
|
|
153
|
-
|
|
154
|
-
|
|
186
|
+
rows: readonly PreparedRepositoryIndex[],
|
|
187
|
+
options: { injectDerivedMaterializationFailure?: boolean } = {},
|
|
188
|
+
): IndexWorkspaceSummary {
|
|
189
|
+
const state = publicationState(rows);
|
|
155
190
|
db.transaction(() => {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
);
|
|
191
|
+
const effects = createPackageInvalidationBatch([]);
|
|
192
|
+
const publishedRepoIds: number[] = [];
|
|
159
193
|
for (const row of state.rows) {
|
|
160
194
|
state.activeRepoId = row.repo.id;
|
|
161
|
-
|
|
195
|
+
if (row.skipped) continue;
|
|
196
|
+
const result = publishPreparedRow(db, row);
|
|
197
|
+
if (result.status === 'failed') {
|
|
198
|
+
recordPublicationFailure(db, state, row, result.error);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
state.indexedCount += 1;
|
|
202
|
+
publishedRepoIds.push(row.repo.id);
|
|
203
|
+
mergePackageInvalidationEffects(effects, result.effects);
|
|
162
204
|
}
|
|
163
205
|
if (options.injectDerivedMaterializationFailure)
|
|
164
206
|
throw new Error('Injected derived materialization failure');
|
|
165
|
-
materializeCdsExtensionOperations(
|
|
207
|
+
materializeCdsExtensionOperations(
|
|
208
|
+
db, workspaceId, state.failedRepoIds,
|
|
209
|
+
);
|
|
210
|
+
const invalidations = createPackageInvalidationBatch(publishedRepoIds);
|
|
211
|
+
mergePackageInvalidationEffects(invalidations, effects);
|
|
166
212
|
finalizePackageTargetInvalidations(db, invalidations);
|
|
167
|
-
|
|
213
|
+
finishCompletedRun(db, runId, state);
|
|
214
|
+
});
|
|
215
|
+
return indexSummary(rows.length, state);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function publicationState(
|
|
219
|
+
rows: readonly PreparedRepositoryIndex[],
|
|
220
|
+
): PublicationState {
|
|
221
|
+
return {
|
|
222
|
+
rows,
|
|
223
|
+
fileCount: rows.reduce((total, row) => total + row.fileCount, 0),
|
|
224
|
+
diagnosticCount: rows.reduce(
|
|
225
|
+
(total, row) => total + row.diagnosticCount, 0,
|
|
226
|
+
),
|
|
227
|
+
skippedCount: rows.filter((row) => row.skipped).length,
|
|
228
|
+
indexedCount: 0,
|
|
229
|
+
publicationFailureCount: 0,
|
|
230
|
+
failedRepoIds: new Set(),
|
|
231
|
+
failedRepos: [],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
type PublicationResult =
|
|
236
|
+
| { status: 'published'; effects: PackageInvalidationBatch }
|
|
237
|
+
| { status: 'failed'; error: unknown };
|
|
238
|
+
|
|
239
|
+
function publishPreparedRow(
|
|
240
|
+
db: Db,
|
|
241
|
+
row: PreparedRepositoryIndex,
|
|
242
|
+
): PublicationResult {
|
|
243
|
+
const effects = createPackageInvalidationBatch([row.repo.id]);
|
|
244
|
+
const outcome = publishOneRepository(db, row, effects);
|
|
245
|
+
if (!outcome.ok && !isPreparedRepositorySnapshotError(outcome.error))
|
|
246
|
+
throw outcome.error;
|
|
247
|
+
return outcome.ok
|
|
248
|
+
? { status: 'published', effects }
|
|
249
|
+
: { status: 'failed', error: outcome.error };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function recordPublicationFailure(
|
|
253
|
+
db: Db,
|
|
254
|
+
state: PublicationState,
|
|
255
|
+
row: PreparedRepositoryIndex,
|
|
256
|
+
error: unknown,
|
|
257
|
+
): void {
|
|
258
|
+
state.failedRepoIds.add(row.repo.id);
|
|
259
|
+
state.failedRepos.push({
|
|
260
|
+
name: row.repo.name,
|
|
261
|
+
code: isPreparedRepositorySnapshotError(error)
|
|
262
|
+
? error.message : 'source_read_failed',
|
|
168
263
|
});
|
|
264
|
+
state.publicationFailureCount += 1;
|
|
169
265
|
}
|
|
170
266
|
|
|
171
|
-
function
|
|
267
|
+
function finishCompletedRun(
|
|
172
268
|
db: Db,
|
|
173
269
|
runId: number,
|
|
174
|
-
state:
|
|
270
|
+
state: PublicationState,
|
|
175
271
|
): void {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
new Date().toISOString(), state.fileCount, state.diagnosticCount, runId,
|
|
272
|
+
const status = completedRunStatus(
|
|
273
|
+
state.rows.length, state.publicationFailureCount,
|
|
179
274
|
);
|
|
275
|
+
const error = status === 'success' ? null
|
|
276
|
+
: `${state.publicationFailureCount} repositories failed index publication.`;
|
|
277
|
+
db.prepare(`UPDATE index_runs SET finished_at=?,status=?,
|
|
278
|
+
file_count=?,diagnostic_count=?,error_message=? WHERE id=?`).run(
|
|
279
|
+
new Date().toISOString(), status, state.fileCount,
|
|
280
|
+
completedDiagnosticCount(state), error, runId,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function completedRunStatus(
|
|
285
|
+
repoCount: number,
|
|
286
|
+
failedCount: number,
|
|
287
|
+
): 'success' | 'partial_failure' | 'failed' {
|
|
288
|
+
if (failedCount === 0) return 'success';
|
|
289
|
+
return failedCount === repoCount ? 'failed' : 'partial_failure';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function completedDiagnosticCount(state: PublicationState): number {
|
|
293
|
+
return state.diagnosticCount + state.publicationFailureCount;
|
|
180
294
|
}
|
|
181
295
|
|
|
182
296
|
function finishFailedRun(
|
|
@@ -194,19 +308,17 @@ function finishFailedRun(
|
|
|
194
308
|
|
|
195
309
|
function indexSummary(
|
|
196
310
|
repoCount: number,
|
|
197
|
-
state:
|
|
198
|
-
): {
|
|
199
|
-
repoCount: number;
|
|
200
|
-
indexedCount: number;
|
|
201
|
-
skippedCount: number;
|
|
202
|
-
fileCount: number;
|
|
203
|
-
diagnosticCount: number;
|
|
204
|
-
} {
|
|
311
|
+
state: PublicationState,
|
|
312
|
+
): IndexWorkspaceSummary {
|
|
205
313
|
return {
|
|
206
314
|
repoCount,
|
|
207
|
-
indexedCount:
|
|
315
|
+
indexedCount: state.indexedCount,
|
|
208
316
|
skippedCount: state.skippedCount,
|
|
317
|
+
failedCount: state.publicationFailureCount,
|
|
318
|
+
failedRepos: [...state.failedRepos].sort((left, right) =>
|
|
319
|
+
binaryCompare(left.name, right.name)
|
|
320
|
+
|| binaryCompare(left.code, right.code)),
|
|
209
321
|
fileCount: state.fileCount,
|
|
210
|
-
diagnosticCount: state
|
|
322
|
+
diagnosticCount: completedDiagnosticCount(state),
|
|
211
323
|
};
|
|
212
324
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { Db } from '../db/connection.js';
|
|
2
|
+
import {
|
|
3
|
+
linkEventTemplate,
|
|
4
|
+
type LinkedEventTemplate,
|
|
5
|
+
} from './006-event-template-link.js';
|
|
2
6
|
|
|
3
7
|
export interface SubscriptionHandlerLinkSummary {
|
|
4
8
|
edgeCount: number;
|
|
@@ -20,6 +24,7 @@ interface SubscriptionRow {
|
|
|
20
24
|
startOffset?: number | null;
|
|
21
25
|
endOffset?: number | null;
|
|
22
26
|
confidence: number;
|
|
27
|
+
unresolvedReason?: string | null;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
interface HandlerCallRow {
|
|
@@ -58,7 +63,7 @@ function subscriptionRows(db: Db, workspaceId: number): SubscriptionRow[] {
|
|
|
58
63
|
c.source_symbol_id sourceSymbolId,c.event_name_expr eventName,
|
|
59
64
|
c.source_file sourceFile,c.source_line sourceLine,
|
|
60
65
|
c.call_site_start_offset startOffset,c.call_site_end_offset endOffset,
|
|
61
|
-
c.confidence
|
|
66
|
+
c.confidence,c.unresolved_reason unresolvedReason
|
|
62
67
|
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
63
68
|
WHERE r.workspace_id=? AND c.call_type='async_subscribe'
|
|
64
69
|
AND json_extract(c.evidence_json,'$.handlerReferenceStatus')
|
|
@@ -202,11 +207,16 @@ function missingAssociation(
|
|
|
202
207
|
function evidenceFor(
|
|
203
208
|
subscription: SubscriptionRow,
|
|
204
209
|
association: HandlerAssociation,
|
|
210
|
+
event: LinkedEventTemplate,
|
|
205
211
|
): Record<string, unknown> {
|
|
206
212
|
const call: Partial<HandlerCallRow> = association.call ?? {};
|
|
207
213
|
const symbolCallReason = boundedSymbolCallReason(call.unresolvedReason);
|
|
208
214
|
return {
|
|
209
215
|
eventName: subscription.eventName,
|
|
216
|
+
...(event.substitution.placeholders.length > 0 ? {
|
|
217
|
+
effectiveEventName: event.targetId,
|
|
218
|
+
eventTemplateResolution: event.substitution,
|
|
219
|
+
} : {}),
|
|
210
220
|
associationBasis: 'exact_subscription_call_span',
|
|
211
221
|
dispatchScope: 'workspace_event_name_only',
|
|
212
222
|
subscribeCallId: subscription.id,
|
|
@@ -253,22 +263,27 @@ function insertAssociationEdge(
|
|
|
253
263
|
generation: number,
|
|
254
264
|
subscription: SubscriptionRow,
|
|
255
265
|
association: HandlerAssociation,
|
|
266
|
+
event: LinkedEventTemplate,
|
|
256
267
|
): void {
|
|
268
|
+
const status = event.isDynamic ? 'unresolved' : association.status;
|
|
269
|
+
const reason = event.isDynamic
|
|
270
|
+
? 'event_template_variables_missing'
|
|
271
|
+
: association.reasonCode ?? association.call?.unresolvedReason ?? null;
|
|
257
272
|
db.prepare(`INSERT INTO graph_edges(
|
|
258
273
|
workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,
|
|
259
274
|
confidence,evidence_json,is_dynamic,unresolved_reason,generation
|
|
260
275
|
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`).run(
|
|
261
276
|
workspaceId,
|
|
262
277
|
'EVENT_SUBSCRIPTION_HANDLED_BY',
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
278
|
+
status,
|
|
279
|
+
event.targetKind,
|
|
280
|
+
event.targetId,
|
|
266
281
|
association.toKind,
|
|
267
282
|
association.toId,
|
|
268
283
|
association.call?.confidence ?? subscription.confidence,
|
|
269
|
-
JSON.stringify(evidenceFor(subscription, association)),
|
|
270
|
-
0,
|
|
271
|
-
|
|
284
|
+
JSON.stringify(evidenceFor(subscription, association, event)),
|
|
285
|
+
event.isDynamic ? 1 : 0,
|
|
286
|
+
reason,
|
|
272
287
|
generation,
|
|
273
288
|
);
|
|
274
289
|
}
|
|
@@ -277,6 +292,7 @@ export function linkEventSubscriptionHandlers(
|
|
|
277
292
|
db: Db,
|
|
278
293
|
workspaceId: number,
|
|
279
294
|
generation: number,
|
|
295
|
+
variables: Record<string, string> = {},
|
|
280
296
|
): SubscriptionHandlerLinkSummary {
|
|
281
297
|
const summary: SubscriptionHandlerLinkSummary = {
|
|
282
298
|
edgeCount: 0,
|
|
@@ -286,16 +302,21 @@ export function linkEventSubscriptionHandlers(
|
|
|
286
302
|
missingAssociationCount: 0,
|
|
287
303
|
};
|
|
288
304
|
for (const subscription of subscriptionRows(db, workspaceId)) {
|
|
305
|
+
const event = linkEventTemplate(
|
|
306
|
+
subscription.eventName, variables,
|
|
307
|
+
subscription.unresolvedReason ?? undefined,
|
|
308
|
+
);
|
|
289
309
|
const association = associationFor(
|
|
290
310
|
subscription, roleSiteRows(db, subscription),
|
|
291
311
|
);
|
|
292
312
|
insertAssociationEdge(
|
|
293
|
-
db, workspaceId, generation, subscription, association,
|
|
313
|
+
db, workspaceId, generation, subscription, association, event,
|
|
294
314
|
);
|
|
315
|
+
const status = event.isDynamic ? 'unresolved' : association.status;
|
|
295
316
|
summary.edgeCount += 1;
|
|
296
|
-
summary.resolvedCount +=
|
|
297
|
-
summary.ambiguousCount +=
|
|
298
|
-
summary.unresolvedCount +=
|
|
317
|
+
summary.resolvedCount += status === 'resolved' ? 1 : 0;
|
|
318
|
+
summary.ambiguousCount += status === 'ambiguous' ? 1 : 0;
|
|
319
|
+
summary.unresolvedCount += status === 'unresolved' ? 1 : 0;
|
|
299
320
|
summary.missingAssociationCount += association.missing ? 1 : 0;
|
|
300
321
|
}
|
|
301
322
|
return summary;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Db } from '../db/connection.js';
|
|
2
|
+
import {
|
|
3
|
+
substituteVariables,
|
|
4
|
+
type RuntimeSubstitution,
|
|
5
|
+
} from './dynamic-edge-resolver.js';
|
|
6
|
+
|
|
7
|
+
export interface LinkedEventTemplate {
|
|
8
|
+
targetId: string;
|
|
9
|
+
targetKind: 'event' | 'event_candidate';
|
|
10
|
+
status: 'terminal' | 'dynamic';
|
|
11
|
+
isDynamic: boolean;
|
|
12
|
+
unresolvedReason?: string;
|
|
13
|
+
substitution: RuntimeSubstitution;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function linkEventTemplate(
|
|
17
|
+
template: string,
|
|
18
|
+
variables: Record<string, string>,
|
|
19
|
+
parserReason?: string,
|
|
20
|
+
): LinkedEventTemplate {
|
|
21
|
+
const substitution = substituteVariables(template, variables);
|
|
22
|
+
const missing = substitution.missing.length > 0;
|
|
23
|
+
const unsupportedDynamic = substitution.placeholders.length === 0
|
|
24
|
+
&& parserReason !== undefined;
|
|
25
|
+
const dynamic = missing || unsupportedDynamic;
|
|
26
|
+
return {
|
|
27
|
+
targetId: dynamic
|
|
28
|
+
? `Event: ${substitution.effective ?? template}`
|
|
29
|
+
: substitution.effective ?? template,
|
|
30
|
+
targetKind: dynamic ? 'event_candidate' : 'event',
|
|
31
|
+
status: dynamic ? 'dynamic' : 'terminal',
|
|
32
|
+
isDynamic: dynamic,
|
|
33
|
+
unresolvedReason: missing
|
|
34
|
+
? `Dynamic target requires runtime variable overrides: ${
|
|
35
|
+
substitution.missing.join(', ')}`
|
|
36
|
+
: unsupportedDynamic ? parserReason : undefined,
|
|
37
|
+
substitution,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function insertEventCallEdge(
|
|
42
|
+
db: Db,
|
|
43
|
+
workspaceId: number,
|
|
44
|
+
generation: number,
|
|
45
|
+
call: Record<string, unknown>,
|
|
46
|
+
variables: Record<string, string>,
|
|
47
|
+
evidence: Record<string, unknown>,
|
|
48
|
+
): { status: string; callType: string } {
|
|
49
|
+
const callType = String(call.call_type);
|
|
50
|
+
const event = linkEventTemplate(
|
|
51
|
+
String(call.event_name_expr ?? ''), variables,
|
|
52
|
+
typeof call.unresolved_reason === 'string'
|
|
53
|
+
? call.unresolved_reason : undefined,
|
|
54
|
+
);
|
|
55
|
+
const edgeType = event.isDynamic
|
|
56
|
+
? 'DYNAMIC_EDGE_CANDIDATE'
|
|
57
|
+
: callType === 'async_emit'
|
|
58
|
+
? 'HANDLER_EMITS_EVENT' : 'EVENT_CONSUMED_BY_HANDLER';
|
|
59
|
+
const eventEvidence = event.substitution.placeholders.length > 0
|
|
60
|
+
? { ...evidence, eventTemplateResolution: event.substitution }
|
|
61
|
+
: evidence;
|
|
62
|
+
db.prepare(`INSERT INTO graph_edges(
|
|
63
|
+
workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,
|
|
64
|
+
confidence,evidence_json,is_dynamic,unresolved_reason,generation
|
|
65
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`).run(
|
|
66
|
+
workspaceId, edgeType, event.status, 'call', String(call.id),
|
|
67
|
+
event.targetKind, event.targetId, Number(call.confidence ?? 0.2),
|
|
68
|
+
JSON.stringify(eventEvidence), event.isDynamic ? 1 : 0,
|
|
69
|
+
event.unresolvedReason ?? null, generation,
|
|
70
|
+
);
|
|
71
|
+
return { status: event.status, callType };
|
|
72
|
+
}
|