@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
|
@@ -42,6 +42,12 @@ function isFunctionLike(node: ts.Node): node is ts.FunctionLikeDeclaration {
|
|
|
42
42
|
function exported(node: ts.Node): boolean {
|
|
43
43
|
return Boolean(ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export);
|
|
44
44
|
}
|
|
45
|
+
function defaultExported(node: ts.Node): boolean {
|
|
46
|
+
return Boolean(
|
|
47
|
+
ts.getCombinedModifierFlags(node as ts.Declaration)
|
|
48
|
+
& ts.ModifierFlags.Default,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
45
51
|
function isPublicClassMethod(node: ts.MethodDeclaration): boolean {
|
|
46
52
|
const flags = ts.getCombinedModifierFlags(node);
|
|
47
53
|
return (flags & ts.ModifierFlags.Private) === 0 && (flags & ts.ModifierFlags.Protected) === 0;
|
|
@@ -141,6 +147,7 @@ interface SymbolCollection {
|
|
|
141
147
|
exportNames: Map<string, string>;
|
|
142
148
|
objectExports: Set<string>;
|
|
143
149
|
exportedClasses: Set<string>;
|
|
150
|
+
defaultExportedClasses: Set<string>;
|
|
144
151
|
declaredClasses: Set<string>;
|
|
145
152
|
proxies: Map<string, SymbolCallProxy[]>;
|
|
146
153
|
instances: Map<string, SymbolClassInstance[]>;
|
|
@@ -153,7 +160,8 @@ function symbolSourceEvidence(
|
|
|
153
160
|
parentRoot: string;
|
|
154
161
|
qualifiedName: string;
|
|
155
162
|
declaredExportName?: string;
|
|
156
|
-
classContainerExported: boolean;
|
|
163
|
+
classContainerExported: boolean; classContainerDefaultExported: boolean;
|
|
164
|
+
classMemberExported: boolean;
|
|
157
165
|
objectExported: boolean;
|
|
158
166
|
evidence?: Record<string, unknown>;
|
|
159
167
|
},
|
|
@@ -168,6 +176,8 @@ function symbolSourceEvidence(
|
|
|
168
176
|
if (options.classContainerExported && ts.isMethodDeclaration(node)
|
|
169
177
|
&& isPublicClassMethod(node)) return {
|
|
170
178
|
source: 'exported_class_instance_member', exportedClass: options.parentRoot,
|
|
179
|
+
exportedClassExportKind: options.classContainerDefaultExported
|
|
180
|
+
? 'default' : 'named',
|
|
171
181
|
memberKind: 'class_method',
|
|
172
182
|
};
|
|
173
183
|
if (options.declaredExportName) return {
|
|
@@ -201,6 +211,7 @@ interface SymbolNames {
|
|
|
201
211
|
declaredExportName?: string;
|
|
202
212
|
objectExported: boolean;
|
|
203
213
|
classContainerExported: boolean;
|
|
214
|
+
classContainerDefaultExported: boolean;
|
|
204
215
|
classMemberExported: boolean;
|
|
205
216
|
effectiveName?: string;
|
|
206
217
|
}
|
|
@@ -219,6 +230,24 @@ function exportedClassMember(
|
|
|
219
230
|
&& isPublicClassMethod(node);
|
|
220
231
|
}
|
|
221
232
|
|
|
233
|
+
function classExportState(
|
|
234
|
+
collection: SymbolCollection,
|
|
235
|
+
parentName: string | undefined,
|
|
236
|
+
parentRoot: string,
|
|
237
|
+
): {
|
|
238
|
+
classContainerExported: boolean;
|
|
239
|
+
classContainerDefaultExported: boolean;
|
|
240
|
+
} {
|
|
241
|
+
return {
|
|
242
|
+
classContainerExported: Boolean(
|
|
243
|
+
parentName && collection.exportedClasses.has(parentRoot),
|
|
244
|
+
),
|
|
245
|
+
classContainerDefaultExported: Boolean(
|
|
246
|
+
parentName && collection.defaultExportedClasses.has(parentRoot),
|
|
247
|
+
),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
222
251
|
function symbolNames(
|
|
223
252
|
collection: SymbolCollection,
|
|
224
253
|
kind: string,
|
|
@@ -238,12 +267,11 @@ function symbolNames(
|
|
|
238
267
|
const classMemberExported = exportedClassMember(
|
|
239
268
|
collection, kind, parentName, parentRoot, node,
|
|
240
269
|
);
|
|
241
|
-
const
|
|
242
|
-
parentName && collection.exportedClasses.has(parentRoot),
|
|
243
|
-
);
|
|
270
|
+
const classState = classExportState(collection, parentName, parentRoot);
|
|
244
271
|
return {
|
|
245
272
|
parentRoot, declaredExportName, qualifiedName,
|
|
246
|
-
objectExported,
|
|
273
|
+
objectExported, ...classState,
|
|
274
|
+
classMemberExported,
|
|
247
275
|
effectiveName: classMemberExported || objectExported
|
|
248
276
|
? qualifiedName : declaredExportName,
|
|
249
277
|
};
|
|
@@ -266,6 +294,7 @@ function addExecutableSymbol(
|
|
|
266
294
|
qualifiedName: names.qualifiedName,
|
|
267
295
|
declaredExportName: names.declaredExportName,
|
|
268
296
|
classContainerExported: names.classContainerExported,
|
|
297
|
+
classContainerDefaultExported: names.classContainerDefaultExported,
|
|
269
298
|
classMemberExported: names.classMemberExported,
|
|
270
299
|
objectExported: names.objectExported,
|
|
271
300
|
evidence,
|
|
@@ -457,6 +486,8 @@ function collectClassDeclaration(
|
|
|
457
486
|
collection.declaredClasses.add(node.name.text);
|
|
458
487
|
if (exported(node) || collection.exportNames.has(node.name.text))
|
|
459
488
|
collection.exportedClasses.add(node.name.text);
|
|
489
|
+
if (defaultExported(node))
|
|
490
|
+
collection.defaultExportedClasses.add(node.name.text);
|
|
460
491
|
for (const member of node.members)
|
|
461
492
|
visitDeclaredSymbol(collection, member, node.name.text);
|
|
462
493
|
return true;
|
|
@@ -553,6 +584,7 @@ function createCollection(
|
|
|
553
584
|
importBindings: collectSymbolImportBindings(source),
|
|
554
585
|
exportNames: exportDeclarations(source),
|
|
555
586
|
objectExports: new Set(), exportedClasses: new Set(),
|
|
587
|
+
defaultExportedClasses: new Set(),
|
|
556
588
|
declaredClasses: new Set(), proxies: new Map(), instances: new Map(),
|
|
557
589
|
};
|
|
558
590
|
}
|
|
@@ -15,6 +15,7 @@ export function implementationStartDiagnostic(
|
|
|
15
15
|
message: `Indexed operation matched but implementation edge is ${String(
|
|
16
16
|
edge.status ?? 'unresolved',
|
|
17
17
|
)}`,
|
|
18
|
+
selectorKind: 'operation',
|
|
18
19
|
resolutionStage: 'implementation',
|
|
19
20
|
resolutionStatus: edge.status === 'ambiguous'
|
|
20
21
|
? 'ambiguous_implementation'
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Db } from '../db/connection.js';
|
|
2
2
|
import {
|
|
3
|
+
matchRuntimeTemplate,
|
|
4
|
+
substituteVariables,
|
|
5
|
+
} from '../linker/dynamic-edge-resolver.js';
|
|
6
|
+
import {
|
|
7
|
+
compareBinary,
|
|
3
8
|
type TraversalScopeScheduler,
|
|
4
9
|
type TraversalScopeState,
|
|
5
10
|
} from './010-traversal-scope.js';
|
|
@@ -53,6 +58,8 @@ export interface EventSubscriberTransition {
|
|
|
53
58
|
candidateCount: number;
|
|
54
59
|
symbolCallUnresolvedReason?: string;
|
|
55
60
|
omittedSymbolCallUnresolvedReasonCharacterCount?: number;
|
|
61
|
+
matchStrategy?: string;
|
|
62
|
+
dispatchCertainty?: string;
|
|
56
63
|
handler?: EventSubscriberSymbolTarget;
|
|
57
64
|
}
|
|
58
65
|
|
|
@@ -60,6 +67,7 @@ export interface EventSubscriberTransitionQuery {
|
|
|
60
67
|
workspaceId: number;
|
|
61
68
|
graphGeneration: number;
|
|
62
69
|
eventName: string;
|
|
70
|
+
vars?: Record<string, string>;
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
export type EventBodyExpansion =
|
|
@@ -156,8 +164,8 @@ export function eventTransitionEvidence(
|
|
|
156
164
|
subscribeCallId: transition.subscribeCallId,
|
|
157
165
|
symbolCallId: transition.symbolCallId,
|
|
158
166
|
eventName: transition.eventName,
|
|
159
|
-
matchStrategy: 'workspace_exact_event_name',
|
|
160
|
-
dispatchCertainty: 'static_name_only',
|
|
167
|
+
matchStrategy: transition.matchStrategy ?? 'workspace_exact_event_name',
|
|
168
|
+
dispatchCertainty: transition.dispatchCertainty ?? 'static_name_only',
|
|
161
169
|
associationBasis: transition.associationBasis,
|
|
162
170
|
dispatchScope: transition.dispatchScope,
|
|
163
171
|
roleSiteMatchCount: transition.roleSiteMatchCount,
|
|
@@ -194,7 +202,8 @@ export function loadEventSubscriberTransitions(
|
|
|
194
202
|
query: EventSubscriberTransitionQuery,
|
|
195
203
|
): EventSubscriberTransition[] {
|
|
196
204
|
const rows = db.prepare(`SELECT ge.id graphEdgeId,ge.generation graphGeneration,
|
|
197
|
-
ge.
|
|
205
|
+
ge.from_kind fromKind,ge.from_id eventName,ge.status,
|
|
206
|
+
ge.to_kind targetKind,ge.to_id targetId,
|
|
198
207
|
ge.confidence,ge.unresolved_reason unresolvedReason,ge.evidence_json evidenceJson,
|
|
199
208
|
subscribe.id subscribeCallId,subscribe.repo_id subscriptionRepoId,
|
|
200
209
|
subscribe.source_file sourceFile,subscribe.source_line sourceLine,
|
|
@@ -218,19 +227,100 @@ export function loadEventSubscriberTransitions(
|
|
|
218
227
|
LEFT JOIN repositories handler_repo
|
|
219
228
|
ON handler_repo.id=handler.repo_id AND handler_repo.workspace_id=ge.workspace_id
|
|
220
229
|
WHERE ge.workspace_id=? AND ge.generation=?
|
|
221
|
-
AND ge.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
222
|
-
AND ge.
|
|
230
|
+
AND ge.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
231
|
+
AND ge.from_kind IN ('event','event_candidate')
|
|
223
232
|
ORDER BY COALESCE(subscription_repo.name,'') COLLATE BINARY,
|
|
224
233
|
COALESCE(subscription_repo.id,0),COALESCE(subscribe.source_file,'') COLLATE BINARY,
|
|
225
234
|
subscribe.call_site_start_offset,subscribe.call_site_end_offset,ge.id`).all(
|
|
226
|
-
query.workspaceId, query.graphGeneration,
|
|
235
|
+
query.workspaceId, query.graphGeneration,
|
|
227
236
|
);
|
|
228
|
-
return rows.flatMap((
|
|
229
|
-
const
|
|
237
|
+
return rows.flatMap((raw) => {
|
|
238
|
+
const row = eventRowForQuery(raw, query);
|
|
239
|
+
const transition = row ? transitionFromRow(row) : undefined;
|
|
230
240
|
return transition ? [transition] : [];
|
|
231
241
|
});
|
|
232
242
|
}
|
|
233
243
|
|
|
244
|
+
function eventRowForQuery(
|
|
245
|
+
row: Record<string, unknown>,
|
|
246
|
+
query: EventSubscriberTransitionQuery,
|
|
247
|
+
): Record<string, unknown> | undefined {
|
|
248
|
+
const variables = query.vars;
|
|
249
|
+
if (variables === undefined) return exactPersistedEventRow(row, query);
|
|
250
|
+
const evidence = parseEvidence(row.evidenceJson);
|
|
251
|
+
const resolution = isRecord(evidence.eventTemplateResolution)
|
|
252
|
+
? evidence.eventTemplateResolution : {};
|
|
253
|
+
if (typeof resolution.original !== 'string')
|
|
254
|
+
return exactPersistedEventRow(row, query);
|
|
255
|
+
return runtimeMatchedEventRow(
|
|
256
|
+
row, query, evidence, resolution.original, variables,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function exactPersistedEventRow(
|
|
261
|
+
row: Record<string, unknown>,
|
|
262
|
+
query: EventSubscriberTransitionQuery,
|
|
263
|
+
): Record<string, unknown> | undefined {
|
|
264
|
+
return row.fromKind === 'event' && row.eventName === query.eventName
|
|
265
|
+
? row : undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function runtimeMatchedEventRow(
|
|
269
|
+
row: Record<string, unknown>,
|
|
270
|
+
query: EventSubscriberTransitionQuery,
|
|
271
|
+
evidence: Record<string, unknown>,
|
|
272
|
+
template: string,
|
|
273
|
+
variables: Record<string, string>,
|
|
274
|
+
): Record<string, unknown> | undefined {
|
|
275
|
+
const substitution = substituteVariables(template, variables);
|
|
276
|
+
if (substitution.missing.length > 0
|
|
277
|
+
|| substitution.effective !== query.eventName) return undefined;
|
|
278
|
+
const resolvedAssociation = evidence.associationStatus === 'resolved';
|
|
279
|
+
return {
|
|
280
|
+
...row,
|
|
281
|
+
eventName: query.eventName,
|
|
282
|
+
status: typeof evidence.associationStatus === 'string'
|
|
283
|
+
? evidence.associationStatus : row.status,
|
|
284
|
+
unresolvedReason: resolvedAssociation ? null : row.unresolvedReason,
|
|
285
|
+
evidenceJson: JSON.stringify({
|
|
286
|
+
...evidence,
|
|
287
|
+
eventSubscriptionRuntimeSubstitution: substitution,
|
|
288
|
+
matchStrategy: 'workspace_exact_event_name_after_runtime_substitution',
|
|
289
|
+
dispatchCertainty: 'runtime_variables_exact',
|
|
290
|
+
}),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function eventSubscriberMissingVariables(
|
|
295
|
+
db: Db,
|
|
296
|
+
query: EventSubscriberTransitionQuery,
|
|
297
|
+
): string[] {
|
|
298
|
+
if (query.vars === undefined) return [];
|
|
299
|
+
const variables = query.vars;
|
|
300
|
+
const rows = db.prepare(`SELECT ge.from_id eventName,
|
|
301
|
+
ge.evidence_json evidenceJson FROM graph_edges ge
|
|
302
|
+
WHERE ge.workspace_id=? AND ge.generation=?
|
|
303
|
+
AND ge.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
304
|
+
AND ge.from_kind='event_candidate'`).all(
|
|
305
|
+
query.workspaceId, query.graphGeneration,
|
|
306
|
+
);
|
|
307
|
+
const missing = rows.flatMap((row) => {
|
|
308
|
+
const evidence = parseEvidence(row.evidenceJson);
|
|
309
|
+
const resolution = isRecord(evidence.eventTemplateResolution)
|
|
310
|
+
? evidence.eventTemplateResolution : {};
|
|
311
|
+
const template = typeof resolution.original === 'string'
|
|
312
|
+
? resolution.original : undefined;
|
|
313
|
+
if (!template) return [];
|
|
314
|
+
const substitution = substituteVariables(template, variables);
|
|
315
|
+
if (substitution.missing.length === 0
|
|
316
|
+
|| matchRuntimeTemplate(
|
|
317
|
+
substitution.effective, query.eventName,
|
|
318
|
+
) === undefined) return [];
|
|
319
|
+
return substitution.missing;
|
|
320
|
+
});
|
|
321
|
+
return [...new Set(missing)].sort(compareBinary);
|
|
322
|
+
}
|
|
323
|
+
|
|
234
324
|
function transitionFromRow(
|
|
235
325
|
row: Record<string, unknown>,
|
|
236
326
|
): EventSubscriberTransition | undefined {
|
|
@@ -290,6 +380,8 @@ function associationEvidence(
|
|
|
290
380
|
omittedSymbolCallUnresolvedReasonCharacterCount: symbolCallUnresolvedReason
|
|
291
381
|
? nonNegativeCount(evidence.omittedSymbolCallUnresolvedReasonCharacterCount)
|
|
292
382
|
: undefined,
|
|
383
|
+
matchStrategy: stringValue(evidence.matchStrategy),
|
|
384
|
+
dispatchCertainty: stringValue(evidence.dispatchCertainty),
|
|
293
385
|
};
|
|
294
386
|
}
|
|
295
387
|
|
|
@@ -253,12 +253,11 @@ function hasExactDispatch(db: Db, calls: RootCallRow[]): boolean {
|
|
|
253
253
|
AND subscriber.generation=emitted.generation
|
|
254
254
|
AND subscriber.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
|
|
255
255
|
AND subscriber.from_kind='event'
|
|
256
|
-
AND subscriber.from_id COLLATE BINARY
|
|
256
|
+
AND subscriber.from_id COLLATE BINARY=emitted.to_id COLLATE BINARY)
|
|
257
257
|
LIMIT 1`);
|
|
258
258
|
return calls.some((call) => call.callType === 'async_emit'
|
|
259
|
-
&& typeof call.eventName === 'string'
|
|
260
259
|
&& Boolean(match.get(call.workspaceId, call.graphGeneration,
|
|
261
|
-
String(call.id)
|
|
260
|
+
String(call.id))));
|
|
262
261
|
}
|
|
263
262
|
|
|
264
263
|
function workspaceAmbiguityDiagnostic(db: Db): Record<string, unknown> {
|
|
@@ -82,6 +82,7 @@ export interface CompactDecisionInput {
|
|
|
82
82
|
implementationStrategy?: string;
|
|
83
83
|
implementationGuided?: boolean;
|
|
84
84
|
implementationContextual?: boolean;
|
|
85
|
+
tiedCandidateRepos?: CompactReferenceGroupV1;
|
|
85
86
|
reasonCode?: string;
|
|
86
87
|
eventMatchStrategy?: string;
|
|
87
88
|
dispatchCertainty?: string;
|
|
@@ -225,6 +226,7 @@ export interface CompactDecisionV1 {
|
|
|
225
226
|
implementationStrategy?: string;
|
|
226
227
|
implementationGuided?: boolean;
|
|
227
228
|
implementationContextual?: boolean;
|
|
229
|
+
tiedCandidateRepos?: CompactReferenceGroupV1;
|
|
228
230
|
eventMatchStrategy?: string;
|
|
229
231
|
dispatchCertainty?: string;
|
|
230
232
|
eventSubscriptionCount?: number;
|
|
@@ -247,6 +249,10 @@ export interface CompactEdgeDetailsV1 {
|
|
|
247
249
|
|
|
248
250
|
export interface CompactDiagnosticDetailsV1 {
|
|
249
251
|
reasonCode?: string;
|
|
252
|
+
tiedCandidateRepos?: CompactReferenceGroupV1;
|
|
253
|
+
selectorKind?: string;
|
|
254
|
+
selectorSuggestions?: CompactReferenceGroupV1;
|
|
255
|
+
invalidFactCategories?: CompactReferenceGroupV1;
|
|
250
256
|
missingVariableNames?: string[];
|
|
251
257
|
missingVariableCount?: number;
|
|
252
258
|
shownMissingVariableCount?: number;
|
|
@@ -8,6 +8,12 @@ import type {
|
|
|
8
8
|
CompactStatus,
|
|
9
9
|
CompactTraceObserver,
|
|
10
10
|
} from './014-compact-contract.js';
|
|
11
|
+
import { implementationHintSuggestionProjection } from
|
|
12
|
+
'./implementation-hints.js';
|
|
13
|
+
import {
|
|
14
|
+
isSafeCompactReferenceName,
|
|
15
|
+
projectCompactReferenceGroup,
|
|
16
|
+
} from './021-compact-decision-normalization.js';
|
|
11
17
|
|
|
12
18
|
export interface TraceEdgeSemantics {
|
|
13
19
|
source: CompactSemanticEndpoint;
|
|
@@ -111,8 +117,7 @@ export function semanticGraphTarget(
|
|
|
111
117
|
): CompactSemanticEndpoint {
|
|
112
118
|
if (row.to_kind === 'event') return {
|
|
113
119
|
kind: 'event', workspaceId,
|
|
114
|
-
eventName:
|
|
115
|
-
? call.event_name_expr : row.to_id,
|
|
120
|
+
eventName: row.to_id,
|
|
116
121
|
};
|
|
117
122
|
const id = positiveNumber(row.to_id);
|
|
118
123
|
if (row.to_kind === 'operation')
|
|
@@ -198,7 +203,7 @@ export function compactDecisionFromEvidence(
|
|
|
198
203
|
const authoritativeMissingCount = finiteNumber(
|
|
199
204
|
dynamic.missingVariableCount ?? evidence.missingVariableCount,
|
|
200
205
|
);
|
|
201
|
-
|
|
206
|
+
const decision: CompactDecisionInput = {
|
|
202
207
|
effectiveResolutionStatus: stringValue(effective.status),
|
|
203
208
|
effectiveTarget: targetSummary(effective),
|
|
204
209
|
persistedResolutionStatus: stringValue(persisted.status),
|
|
@@ -221,7 +226,7 @@ export function compactDecisionFromEvidence(
|
|
|
221
226
|
implementationGuided: booleanValue(implementation.guided),
|
|
222
227
|
implementationContextual: booleanValue(
|
|
223
228
|
evidence.contextualImplementationSelected),
|
|
224
|
-
reasonCode:
|
|
229
|
+
reasonCode: compactEvidenceReasonCode(evidence),
|
|
225
230
|
eventMatchStrategy: stringValue(evidence.matchStrategy),
|
|
226
231
|
dispatchCertainty: stringValue(evidence.dispatchCertainty),
|
|
227
232
|
associationStatus: stringValue(evidence.associationStatus),
|
|
@@ -233,6 +238,7 @@ export function compactDecisionFromEvidence(
|
|
|
233
238
|
bodyExpansion: stringValue(evidence.bodyExpansion),
|
|
234
239
|
...overrides,
|
|
235
240
|
};
|
|
241
|
+
return decisionWithTiedCandidates(evidence, decision);
|
|
236
242
|
}
|
|
237
243
|
|
|
238
244
|
export function compactRefs(
|
|
@@ -334,3 +340,54 @@ function firstNumber(...values: unknown[]): number | undefined {
|
|
|
334
340
|
}
|
|
335
341
|
return undefined;
|
|
336
342
|
}
|
|
343
|
+
|
|
344
|
+
export function compactEvidenceReasonCode(
|
|
345
|
+
evidence: Record<string, unknown>,
|
|
346
|
+
): string | undefined {
|
|
347
|
+
return stringValue(evidence.reasonCode ?? evidence.cycleReason)
|
|
348
|
+
?? parserWarningReason(evidence.parserWarning);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function parserWarningReason(value: unknown): string | undefined {
|
|
352
|
+
const warning = recordValue(value);
|
|
353
|
+
const code = stringValue(warning.code);
|
|
354
|
+
if (code && code !== 'parser_warning') return code;
|
|
355
|
+
return stringValue(warning.message);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function tiedCandidateRepositoryGroup(
|
|
359
|
+
evidence: Record<string, unknown>,
|
|
360
|
+
decision: CompactDecisionInput,
|
|
361
|
+
): CompactDecisionInput['tiedCandidateRepos'] {
|
|
362
|
+
if (decision.effectiveResolutionStatus !== 'ambiguous'
|
|
363
|
+
|| (decision.candidateCount ?? 0) <= 1) return undefined;
|
|
364
|
+
const persisted = Array.isArray(evidence.implementationHintSuggestions)
|
|
365
|
+
? evidence.implementationHintSuggestions.filter(
|
|
366
|
+
(value): value is Record<string, unknown> =>
|
|
367
|
+
Boolean(value && typeof value === 'object' && !Array.isArray(value)),
|
|
368
|
+
)
|
|
369
|
+
: [];
|
|
370
|
+
const projection = persisted.length > 0 ? {
|
|
371
|
+
suggestions: persisted,
|
|
372
|
+
suggestionCount: finiteNumber(
|
|
373
|
+
evidence.implementationHintSuggestionCount,
|
|
374
|
+
) ?? persisted.length,
|
|
375
|
+
} : implementationHintSuggestionProjection(
|
|
376
|
+
evidence, Number.MAX_SAFE_INTEGER,
|
|
377
|
+
);
|
|
378
|
+
const repos = projection.suggestions.flatMap((suggestion) =>
|
|
379
|
+
typeof suggestion.implementationRepo === 'string'
|
|
380
|
+
? [suggestion.implementationRepo] : []);
|
|
381
|
+
const group = projectCompactReferenceGroup(
|
|
382
|
+
repos, projection.suggestionCount, isSafeCompactReferenceName,
|
|
383
|
+
);
|
|
384
|
+
return group && group.total > 1 ? group : undefined;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function decisionWithTiedCandidates(
|
|
388
|
+
evidence: Record<string, unknown>,
|
|
389
|
+
decision: CompactDecisionInput,
|
|
390
|
+
): CompactDecisionInput {
|
|
391
|
+
const tiedCandidateRepos = tiedCandidateRepositoryGroup(evidence, decision);
|
|
392
|
+
return tiedCandidateRepos ? { ...decision, tiedCandidateRepos } : decision;
|
|
393
|
+
}
|
|
@@ -111,9 +111,8 @@ function decisionInputNode(
|
|
|
111
111
|
return symbolNode(db, numeric) ?? {};
|
|
112
112
|
if (target.kind === 'handler_method' && numeric !== undefined)
|
|
113
113
|
return handlerNode(db, numeric) ?? {};
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
};
|
|
114
|
+
const decisionTarget = projectCompactDecisionTarget(target.kind, target.id);
|
|
115
|
+
return { key: decisionTarget, decisionTarget, projectedIdentity: true };
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
function resolveEndpoint(
|
|
@@ -2,6 +2,7 @@ import type { TraceEdge } from '../types.js';
|
|
|
2
2
|
import type { CompactSemanticEndpoint, CompactStatus } from './014-compact-contract.js';
|
|
3
3
|
import {
|
|
4
4
|
compactDecisionFromEvidence,
|
|
5
|
+
compactEvidenceReasonCode,
|
|
5
6
|
compactEventStatus,
|
|
6
7
|
compactGraphStatus,
|
|
7
8
|
compactRefs,
|
|
@@ -157,11 +158,11 @@ export function recordOutboundObservation(
|
|
|
157
158
|
source, target,
|
|
158
159
|
status: compactGraphStatus(input.row, input.evidence,
|
|
159
160
|
input.unresolvedReason, input.dynamicMode),
|
|
160
|
-
decision: compactDecisionFromEvidence(input.evidence,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
decision: compactDecisionFromEvidence(input.evidence,
|
|
162
|
+
input.unresolvedReason ? {
|
|
163
|
+
reasonCode: compactEvidenceReasonCode(input.evidence)
|
|
164
|
+
?? 'outbound_target_unresolved',
|
|
165
|
+
} : {}),
|
|
165
166
|
refs: compactRefs({
|
|
166
167
|
graphEdgeId: positiveId(input.evidence.persistedGraphEdgeId),
|
|
167
168
|
outboundCallId: input.call.id,
|
|
@@ -295,11 +296,6 @@ function eventSite(
|
|
|
295
296
|
endOffset: plan.transition.callSiteEndOffset });
|
|
296
297
|
}
|
|
297
298
|
|
|
298
|
-
function safeReasonCode(value: unknown, fallback: string): string {
|
|
299
|
-
return typeof value === 'string' && /^[a-z][a-z0-9_.-]{0,79}$/.test(value)
|
|
300
|
-
? value : fallback;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
299
|
function positiveId(value: unknown): number | string | undefined {
|
|
304
300
|
if (typeof value === 'number') return Number.isFinite(value) && value > 0
|
|
305
301
|
? value : undefined;
|
|
@@ -19,6 +19,9 @@ import type {
|
|
|
19
19
|
} from './014-compact-contract.js';
|
|
20
20
|
import {
|
|
21
21
|
compactMissingRemediation,
|
|
22
|
+
isSafeCompactReferenceName,
|
|
23
|
+
isSafeCompactSelectorSuggestion,
|
|
24
|
+
projectCompactReferenceGroup,
|
|
22
25
|
projectCompactMissingNames,
|
|
23
26
|
type CompactMissingNameProjection,
|
|
24
27
|
} from './021-compact-decision-normalization.js';
|
|
@@ -31,8 +34,19 @@ const compactDiagnosticMessages: Readonly<Record<string, string>> = {
|
|
|
31
34
|
implementation_hint_mismatch: 'The implementation hint did not select one implementation.',
|
|
32
35
|
selected_handler_provenance_mismatch: 'Selected handler provenance did not match its graph target.',
|
|
33
36
|
selected_handler_target_not_found: 'The selected handler target is not indexed.',
|
|
37
|
+
trace_start_ambiguous: 'The trace start selector is ambiguous.',
|
|
38
|
+
trace_start_not_found: 'The trace start selector did not match an indexed start.',
|
|
34
39
|
trace_start_implementation_unresolved: 'The trace start implementation is unresolved.',
|
|
35
40
|
};
|
|
41
|
+
const selectorDiagnosticCodes = new Set([
|
|
42
|
+
'handler_decorators_not_indexed',
|
|
43
|
+
'handler_methods_not_indexed',
|
|
44
|
+
'selector_repo_ambiguous',
|
|
45
|
+
'selector_repo_not_found',
|
|
46
|
+
'trace_start_ambiguous',
|
|
47
|
+
'trace_start_implementation_unresolved',
|
|
48
|
+
'trace_start_not_found',
|
|
49
|
+
]);
|
|
36
50
|
|
|
37
51
|
export function projectCompactDecision(
|
|
38
52
|
input: CompactDecisionInput | undefined,
|
|
@@ -87,6 +101,8 @@ function addImplementationDecision(
|
|
|
87
101
|
out.implementationGuided = input.implementationGuided;
|
|
88
102
|
if (input.implementationContextual !== undefined)
|
|
89
103
|
out.implementationContextual = input.implementationContextual;
|
|
104
|
+
if (input.tiedCandidateRepos)
|
|
105
|
+
out.tiedCandidateRepos = input.tiedCandidateRepos;
|
|
90
106
|
}
|
|
91
107
|
|
|
92
108
|
function addEventDecision(out: CompactDecisionV1, input: CompactDecisionInput): void {
|
|
@@ -243,6 +259,10 @@ function compactDiagnosticDetails(
|
|
|
243
259
|
const out: CompactDiagnosticDetailsV1 = {};
|
|
244
260
|
const reasonCode = compactSafeCode(value.reasonCode);
|
|
245
261
|
if (reasonCode) out.reasonCode = reasonCode;
|
|
262
|
+
if (selectorDiagnosticCodes.has(code)) addDiagnosticSelector(out, value);
|
|
263
|
+
if (code === 'reindex_required') addInvalidFactCategories(out, value);
|
|
264
|
+
if (code === 'implementation_hint_mismatch')
|
|
265
|
+
addImplementationHintCandidates(out, value);
|
|
246
266
|
if (code === 'trace_runtime_variables_missing') addDiagnosticNames(out, value);
|
|
247
267
|
addDiagnosticCounts(out, value);
|
|
248
268
|
const hint = compactDiagnosticRemediation(code, out);
|
|
@@ -255,6 +275,51 @@ function compactDiagnosticDetails(
|
|
|
255
275
|
return out;
|
|
256
276
|
}
|
|
257
277
|
|
|
278
|
+
function addDiagnosticSelector(
|
|
279
|
+
out: CompactDiagnosticDetailsV1,
|
|
280
|
+
value: Record<string, unknown>,
|
|
281
|
+
): void {
|
|
282
|
+
const selectorKind = compactSafeCode(value.selectorKind);
|
|
283
|
+
if (selectorKind) out.selectorKind = selectorKind;
|
|
284
|
+
const suggestions = projectCompactReferenceGroup(
|
|
285
|
+
compactStringArray(value.selectorSuggestions),
|
|
286
|
+
value.selectorSuggestionCount,
|
|
287
|
+
isSafeCompactSelectorSuggestion,
|
|
288
|
+
);
|
|
289
|
+
if (suggestions) out.selectorSuggestions = suggestions;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function addInvalidFactCategories(
|
|
293
|
+
out: CompactDiagnosticDetailsV1,
|
|
294
|
+
value: Record<string, unknown>,
|
|
295
|
+
): void {
|
|
296
|
+
const categories = compactRecordArray(value.invalidFactCategories)
|
|
297
|
+
.flatMap((item) =>
|
|
298
|
+
typeof item.category === 'string' ? [item.category] : []);
|
|
299
|
+
const projection = projectCompactReferenceGroup(
|
|
300
|
+
categories, value.invalidFactCategoryCount,
|
|
301
|
+
(item) => compactSafeCode(item) === item,
|
|
302
|
+
);
|
|
303
|
+
if (projection) out.invalidFactCategories = projection;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function addImplementationHintCandidates(
|
|
307
|
+
out: CompactDiagnosticDetailsV1,
|
|
308
|
+
value: Record<string, unknown>,
|
|
309
|
+
): void {
|
|
310
|
+
const repos = compactRecordArray(value.implementationHintSuggestions)
|
|
311
|
+
.flatMap((item) =>
|
|
312
|
+
typeof item.implementationRepo === 'string'
|
|
313
|
+
? [item.implementationRepo] : []);
|
|
314
|
+
const uniqueCount = new Set(repos.map((repo) => repo.trim())).size;
|
|
315
|
+
if (uniqueCount < 2) return;
|
|
316
|
+
const projection = projectCompactReferenceGroup(
|
|
317
|
+
repos, value.implementationHintSuggestionCount,
|
|
318
|
+
isSafeCompactReferenceName,
|
|
319
|
+
);
|
|
320
|
+
if (projection) out.tiedCandidateRepos = projection;
|
|
321
|
+
}
|
|
322
|
+
|
|
258
323
|
function addDiagnosticNames(
|
|
259
324
|
out: CompactDiagnosticDetailsV1,
|
|
260
325
|
value: Record<string, unknown>,
|
|
@@ -345,7 +410,8 @@ function compactDiagnosticHintCount(
|
|
|
345
410
|
|
|
346
411
|
function compactRemediationHint(code: string): string | undefined {
|
|
347
412
|
if (code === 'provide_runtime_variables') return 'Provide the missing variable names listed in details.';
|
|
348
|
-
if (code === 'select_implementation')
|
|
413
|
+
if (code === 'select_implementation')
|
|
414
|
+
return 'Use --implementation-hint with service, operation, package, repository, family, and repo keys; repo is required.';
|
|
349
415
|
if (code === 'reindex_and_link') return 'Force reindex, then force relink the workspace.';
|
|
350
416
|
if (code === 'inspect_detailed_edge') return 'Inspect the correlated detailed trace edge.';
|
|
351
417
|
return undefined;
|
|
@@ -389,6 +455,13 @@ function compactStringArray(value: unknown): string[] {
|
|
|
389
455
|
? value.filter((item): item is string => typeof item === 'string') : [];
|
|
390
456
|
}
|
|
391
457
|
|
|
458
|
+
function compactRecordArray(value: unknown): Array<Record<string, unknown>> {
|
|
459
|
+
return Array.isArray(value)
|
|
460
|
+
? value.filter((item): item is Record<string, unknown> =>
|
|
461
|
+
Boolean(item && typeof item === 'object' && !Array.isArray(item)))
|
|
462
|
+
: [];
|
|
463
|
+
}
|
|
464
|
+
|
|
392
465
|
function compactArrayLength(value: unknown): number {
|
|
393
466
|
return Array.isArray(value) ? value.length : 0;
|
|
394
467
|
}
|