@saptools/service-flow 0.1.67 → 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 +20 -0
- package/README.md +27 -9
- package/TECHNICAL-NOTE.md +36 -0
- package/dist/chunk-3N3B5KHV.js +19596 -0
- package/dist/chunk-3N3B5KHV.js.map +1 -0
- package/dist/cli.js +2645 -521
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +67 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/001-index-summary.ts +22 -0
- package/src/cli/003-doctor-package-resolution.ts +68 -0
- package/src/cli/doctor.ts +25 -14
- package/src/cli.ts +151 -87
- package/src/db/000-call-fact-repository.ts +499 -340
- package/src/db/001-fact-lifecycle.ts +60 -30
- package/src/db/002-fact-json-inventory.ts +169 -0
- package/src/db/003-current-fact-semantics.ts +699 -0
- package/src/db/004-package-target-invalidation.ts +183 -0
- package/src/db/005-schema-structure.ts +201 -0
- package/src/db/006-relative-symbol-resolution.ts +464 -0
- package/src/db/007-package-fact-semantics.ts +573 -0
- package/src/db/008-relative-fact-semantics.ts +210 -0
- package/src/db/009-binding-fact-semantics.ts +352 -0
- package/src/db/010-package-symbol-surface-semantics.ts +320 -0
- package/src/db/011-symbol-call-semantics.ts +144 -0
- package/src/db/012-binding-reference-proof.ts +268 -0
- package/src/db/013-index-publication-failure.ts +91 -0
- package/src/db/014-binding-helper-provenance.ts +17 -0
- package/src/db/migrations.ts +16 -3
- package/src/db/repositories.ts +130 -6
- package/src/db/schema.ts +4 -2
- package/src/index.ts +12 -0
- package/src/indexer/cds-extension-resolver.ts +27 -3
- package/src/indexer/repository-indexer.ts +135 -13
- package/src/indexer/workspace-indexer.ts +237 -34
- package/src/linker/003-package-import-symbol-resolver.ts +363 -131
- package/src/linker/004-event-subscription-handler-linker.ts +34 -11
- package/src/linker/005-odata-path-structure.ts +371 -0
- 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 +4 -166
- package/src/linker/odata-path-normalizer.ts +273 -180
- package/src/linker/service-resolver.ts +197 -77
- package/src/parsers/000-direct-query-execution.ts +11 -0
- package/src/parsers/002-symbol-import-bindings.ts +516 -0
- package/src/parsers/003-package-public-surface.ts +661 -0
- package/src/parsers/004-fact-identity.ts +108 -0
- package/src/parsers/005-event-subscription-facts.ts +281 -0
- package/src/parsers/006-binding-identity.ts +348 -0
- package/src/parsers/007-source-fact-reconciliation.ts +105 -0
- package/src/parsers/008-package-surface-publication.ts +82 -0
- package/src/parsers/009-symbol-call-facts.ts +528 -0
- package/src/parsers/010-package-public-surface-analysis.ts +352 -0
- package/src/parsers/011-binding-lexical-scope.ts +583 -0
- package/src/parsers/012-package-fact-contract.ts +306 -0
- package/src/parsers/013-executable-body-eligibility.ts +35 -0
- package/src/parsers/014-service-binding-helper-flow.ts +306 -0
- package/src/parsers/015-service-binding-collector.ts +693 -0
- package/src/parsers/016-local-symbol-reference.ts +261 -0
- package/src/parsers/017-symbol-derived-contexts.ts +268 -0
- package/src/parsers/018-package-commonjs-syntax.ts +142 -0
- package/src/parsers/019-binding-assignment-targets.ts +76 -0
- package/src/parsers/020-stable-local-value.ts +217 -0
- package/src/parsers/021-binding-visibility.ts +168 -0
- package/src/parsers/022-outbound-expression-analysis.ts +700 -0
- package/src/parsers/023-outbound-call-classifier.ts +692 -0
- package/src/parsers/operation-path-analysis.ts +6 -1
- package/src/parsers/outbound-call-parser.ts +162 -512
- package/src/parsers/package-json-parser.ts +45 -3
- package/src/parsers/service-binding-parser-helpers.ts +86 -15
- package/src/parsers/service-binding-parser.ts +147 -597
- package/src/parsers/symbol-parser.ts +513 -352
- package/src/trace/002-trace-diagnostics.ts +36 -1
- 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 +15 -17
- package/src/trace/019-trace-edge-semantics.ts +6 -10
- package/src/trace/020-compact-field-projection.ts +122 -38
- package/src/trace/021-compact-decision-normalization.ts +171 -0
- package/src/trace/022-trace-fact-preflight.ts +21 -0
- package/src/trace/023-nested-event-scopes.ts +23 -0
- package/src/trace/024-compact-observation-decision.ts +81 -0
- package/src/trace/025-trace-implementation-scope.ts +123 -0
- package/src/trace/026-trace-start-scope.ts +336 -0
- package/src/trace/027-trace-scope-execution.ts +566 -0
- package/src/trace/028-trace-operation-execution.ts +336 -0
- package/src/trace/029-trace-start-implementation.ts +172 -0
- 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/trace/trace-engine.ts +122 -624
- package/src/types.ts +57 -0
- package/src/utils/001-placeholders.ts +188 -10
- package/src/version.ts +1 -1
- package/dist/chunk-ZQABU7MR.js +0 -12151
- package/dist/chunk-ZQABU7MR.js.map +0 -1
|
@@ -7,7 +7,7 @@ export function loadTraceDiagnostics(
|
|
|
7
7
|
workspaceId?: number,
|
|
8
8
|
): Array<Record<string, unknown>> {
|
|
9
9
|
if (repoId === undefined && !includeWorkspaceDiagnostics) return [];
|
|
10
|
-
|
|
10
|
+
const diagnostics = db.prepare(`SELECT d.repo_id repoId,d.severity,d.code,d.message,
|
|
11
11
|
d.source_file sourceFile,d.source_line sourceLine
|
|
12
12
|
FROM diagnostics d LEFT JOIN repositories r ON r.id=d.repo_id
|
|
13
13
|
WHERE (? IS NULL OR d.repo_id=?)
|
|
@@ -18,6 +18,41 @@ export function loadTraceDiagnostics(
|
|
|
18
18
|
) as Array<
|
|
19
19
|
Record<string, unknown>
|
|
20
20
|
>;
|
|
21
|
+
const pending = packagePendingCount(
|
|
22
|
+
db, repoId, includeWorkspaceDiagnostics, workspaceId,
|
|
23
|
+
);
|
|
24
|
+
if (pending > 0) diagnostics.unshift(packagePendingDiagnostic(pending));
|
|
25
|
+
return diagnostics;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function packagePendingCount(
|
|
29
|
+
db: Db,
|
|
30
|
+
repoId: number | undefined,
|
|
31
|
+
includeWorkspace: boolean,
|
|
32
|
+
workspaceId: number | undefined,
|
|
33
|
+
): number {
|
|
34
|
+
const row = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc
|
|
35
|
+
JOIN repositories r ON r.id=sc.repo_id
|
|
36
|
+
WHERE sc.status='unresolved' AND sc.callee_symbol_id IS NULL
|
|
37
|
+
AND sc.unresolved_reason='package_resolution_pending'
|
|
38
|
+
AND (?=1 OR sc.repo_id=?)
|
|
39
|
+
AND (? IS NULL OR r.workspace_id=?)`).get(
|
|
40
|
+
includeWorkspace ? 1 : 0, repoId, workspaceId, workspaceId,
|
|
41
|
+
);
|
|
42
|
+
return Number(row?.count ?? 0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function packagePendingDiagnostic(count: number): Record<string, unknown> {
|
|
46
|
+
return {
|
|
47
|
+
severity: 'warning',
|
|
48
|
+
code: 'package_import_resolution_pending',
|
|
49
|
+
message: 'Package-import facts await workspace linking.',
|
|
50
|
+
packageResolutionState: 'pre_link_pending',
|
|
51
|
+
pendingPackageImportCount: count,
|
|
52
|
+
graphState: 'stale',
|
|
53
|
+
requiredAction: 'relink',
|
|
54
|
+
remediation: 'Run service-flow link --force for this workspace.',
|
|
55
|
+
};
|
|
21
56
|
}
|
|
22
57
|
|
|
23
58
|
export function prependTraceDiagnostic(
|
|
@@ -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
|
+
}
|
|
@@ -20,10 +20,13 @@ import {
|
|
|
20
20
|
} from './014-compact-contract.js';
|
|
21
21
|
import {
|
|
22
22
|
compactCompleteness, compactSafeCode, compactStatusCounts, compactStatusTotal,
|
|
23
|
-
|
|
23
|
+
projectCompactDecisionTarget, projectCompactDiagnostics,
|
|
24
24
|
projectCompactQuery, projectCompactStart,
|
|
25
|
-
removeEquivalentCompactPersistedDecision,
|
|
26
25
|
} from './020-compact-field-projection.js';
|
|
26
|
+
import {
|
|
27
|
+
projectObservationDecision,
|
|
28
|
+
type CompactDecisionNode,
|
|
29
|
+
} from './024-compact-observation-decision.js';
|
|
27
30
|
|
|
28
31
|
const REFERENCE_LIMIT = 5;
|
|
29
32
|
|
|
@@ -92,29 +95,24 @@ function observationDecision(
|
|
|
92
95
|
input: CompactEdgeObservation,
|
|
93
96
|
target: ResolvedNode,
|
|
94
97
|
): CompactDecisionV1 {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (decision.persistedResolutionStatus && input.decision?.persistedTarget) {
|
|
99
|
-
const persisted = persistedDecisionTarget(db, input.decision.persistedTarget);
|
|
100
|
-
if (persisted) decision.persistedTarget = persisted;
|
|
101
|
-
}
|
|
102
|
-
removeEquivalentCompactPersistedDecision(decision);
|
|
103
|
-
return decision;
|
|
98
|
+
return projectObservationDecision(
|
|
99
|
+
input, target, (value) => decisionInputNode(db, value),
|
|
100
|
+
);
|
|
104
101
|
}
|
|
105
102
|
|
|
106
|
-
function
|
|
103
|
+
function decisionInputNode(
|
|
107
104
|
db: Db,
|
|
108
105
|
target: CompactDecisionTargetInput,
|
|
109
|
-
):
|
|
106
|
+
): CompactDecisionNode {
|
|
110
107
|
const numeric = numericId(target.id);
|
|
111
108
|
if (target.kind === 'operation' && numeric !== undefined)
|
|
112
|
-
return operationNode(db, numeric)
|
|
109
|
+
return operationNode(db, numeric) ?? {};
|
|
113
110
|
if (target.kind === 'symbol' && numeric !== undefined)
|
|
114
|
-
return symbolNode(db, numeric)
|
|
111
|
+
return symbolNode(db, numeric) ?? {};
|
|
115
112
|
if (target.kind === 'handler_method' && numeric !== undefined)
|
|
116
|
-
return handlerNode(db, numeric)
|
|
117
|
-
|
|
113
|
+
return handlerNode(db, numeric) ?? {};
|
|
114
|
+
const decisionTarget = projectCompactDecisionTarget(target.kind, target.id);
|
|
115
|
+
return { key: decisionTarget, decisionTarget, projectedIdentity: true };
|
|
118
116
|
}
|
|
119
117
|
|
|
120
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;
|