@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,11 +1,14 @@
|
|
|
1
1
|
import { compareBinary } from './010-traversal-scope.js';
|
|
2
2
|
import type {
|
|
3
3
|
CompactDecisionV1,
|
|
4
|
+
CompactReferenceGroupV1,
|
|
4
5
|
CompactStatus,
|
|
5
6
|
} from './014-compact-contract.js';
|
|
6
7
|
|
|
7
8
|
export const COMPACT_MISSING_NAME_LIMIT = 8;
|
|
8
9
|
export const COMPACT_MISSING_NAME_MAX_LENGTH = 160;
|
|
10
|
+
export const COMPACT_REFERENCE_VALUE_LIMIT = 5;
|
|
11
|
+
const COMPACT_REFERENCE_VALUE_MAX_LENGTH = 240;
|
|
9
12
|
|
|
10
13
|
export interface CompactMissingNameProjection {
|
|
11
14
|
readonly names: string[];
|
|
@@ -23,11 +26,17 @@ export function projectCompactMissingNames(
|
|
|
23
26
|
values: readonly string[] | undefined,
|
|
24
27
|
authoritativeCount: unknown,
|
|
25
28
|
): CompactMissingNameProjection {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
.
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const candidates = new Map<string, boolean>();
|
|
30
|
+
for (const value of values ?? []) {
|
|
31
|
+
const normalized = value.trim();
|
|
32
|
+
if (!normalized) continue;
|
|
33
|
+
candidates.set(
|
|
34
|
+
normalized,
|
|
35
|
+
(candidates.get(normalized) ?? true) && isSafeCompactMissingName(value),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const raw = [...candidates.keys()].sort(compareBinary);
|
|
39
|
+
const safe = raw.filter((name) => candidates.get(name) === true);
|
|
31
40
|
const names = safe.slice(0, COMPACT_MISSING_NAME_LIMIT);
|
|
32
41
|
const total = Math.max(compactCount(authoritativeCount), raw.length);
|
|
33
42
|
return {
|
|
@@ -37,25 +46,58 @@ export function projectCompactMissingNames(
|
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
export function isSafeCompactMissingName(value: string): boolean {
|
|
49
|
+
if (hasControlCharacter(value)) return false;
|
|
40
50
|
const normalized = value.trim();
|
|
41
51
|
return normalized.length > 0
|
|
42
52
|
&& normalized.length <= COMPACT_MISSING_NAME_MAX_LENGTH
|
|
43
|
-
&& !hasControlCharacter(normalized)
|
|
44
53
|
&& safeMissingName.test(normalized);
|
|
45
54
|
}
|
|
46
55
|
|
|
56
|
+
export function projectCompactReferenceGroup(
|
|
57
|
+
values: readonly string[] | undefined,
|
|
58
|
+
authoritativeCount: unknown,
|
|
59
|
+
isSafe: (value: string) => boolean,
|
|
60
|
+
): CompactReferenceGroupV1 | undefined {
|
|
61
|
+
const candidates = normalizedReferenceValues(values, isSafe);
|
|
62
|
+
const unique = [...candidates.keys()].sort(compareBinary);
|
|
63
|
+
const safe = unique.filter((value) => candidates.get(value) === true);
|
|
64
|
+
const shown = safe.slice(0, COMPACT_REFERENCE_VALUE_LIMIT);
|
|
65
|
+
const total = Math.max(compactCount(authoritativeCount), unique.length);
|
|
66
|
+
if (total === 0) return undefined;
|
|
67
|
+
return {
|
|
68
|
+
values: shown,
|
|
69
|
+
total,
|
|
70
|
+
shown: shown.length,
|
|
71
|
+
omitted: Math.max(0, total - shown.length),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isSafeCompactReferenceName(value: string): boolean {
|
|
76
|
+
if (!safeReferenceText(value, 160)) return false;
|
|
77
|
+
return /^[@A-Za-z0-9_$][@A-Za-z0-9_$./:-]*$/.test(value.trim());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function isSafeCompactSelectorSuggestion(value: string): boolean {
|
|
81
|
+
if (!safeReferenceText(value, COMPACT_REFERENCE_VALUE_MAX_LENGTH))
|
|
82
|
+
return false;
|
|
83
|
+
return /^(?:--(?:repo|service|operation|path|handler) [@A-Za-z0-9_$./:-]+)(?: --(?:repo|service|operation|path|handler) [@A-Za-z0-9_$./:-]+)*$/
|
|
84
|
+
.test(value.trim());
|
|
85
|
+
}
|
|
86
|
+
|
|
47
87
|
export function normalizeCompactDecisionEquivalence(
|
|
48
88
|
decision: CompactDecisionV1,
|
|
49
89
|
status: CompactStatus,
|
|
50
90
|
canonicalTarget: string | undefined,
|
|
51
91
|
targetIdentityEqual = false,
|
|
52
92
|
persistedIdentityEqual = false,
|
|
93
|
+
rawPersistedStatus?: string,
|
|
53
94
|
): void {
|
|
95
|
+
if (decision.effectiveResolutionStatus === status)
|
|
96
|
+
delete decision.effectiveResolutionStatus;
|
|
54
97
|
removeEquivalentPersistedDecision(
|
|
55
98
|
decision, status, canonicalTarget, persistedIdentityEqual,
|
|
99
|
+
rawPersistedStatus,
|
|
56
100
|
);
|
|
57
|
-
if (decision.effectiveResolutionStatus === status)
|
|
58
|
-
delete decision.effectiveResolutionStatus;
|
|
59
101
|
if (targetIdentityEqual && canonicalTarget !== undefined
|
|
60
102
|
&& decision.effectiveTarget === canonicalTarget)
|
|
61
103
|
delete decision.effectiveTarget;
|
|
@@ -77,9 +119,10 @@ function removeEquivalentPersistedDecision(
|
|
|
77
119
|
status: CompactStatus,
|
|
78
120
|
canonicalTarget: string | undefined,
|
|
79
121
|
identityEqual: boolean,
|
|
122
|
+
rawPersistedStatus: string | undefined,
|
|
80
123
|
): void {
|
|
81
124
|
if (!identityEqual
|
|
82
|
-
||
|
|
125
|
+
|| rawPersistedStatus !== status
|
|
83
126
|
|| (decision.effectiveResolutionStatus !== undefined
|
|
84
127
|
&& decision.persistedResolutionStatus
|
|
85
128
|
!== decision.effectiveResolutionStatus))
|
|
@@ -96,6 +139,29 @@ function compactCount(value: unknown): number {
|
|
|
96
139
|
? Math.max(0, Math.floor(value)) : 0;
|
|
97
140
|
}
|
|
98
141
|
|
|
142
|
+
function normalizedReferenceValues(
|
|
143
|
+
values: readonly string[] | undefined,
|
|
144
|
+
isSafe: (value: string) => boolean,
|
|
145
|
+
): Map<string, boolean> {
|
|
146
|
+
const unique = new Map<string, boolean>();
|
|
147
|
+
for (const raw of values ?? []) {
|
|
148
|
+
const value = raw.trim();
|
|
149
|
+
if (!value) continue;
|
|
150
|
+
unique.set(value, (unique.get(value) ?? true) && isSafe(raw));
|
|
151
|
+
}
|
|
152
|
+
return unique;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function safeReferenceText(value: string, maxLength: number): boolean {
|
|
156
|
+
if (hasControlCharacter(value)) return false;
|
|
157
|
+
const normalized = value.trim();
|
|
158
|
+
return normalized.length > 0
|
|
159
|
+
&& normalized.length <= maxLength
|
|
160
|
+
&& !/^[a-z]+:\/\//i.test(normalized)
|
|
161
|
+
&& !/(?:^|[^A-Za-z0-9])(?:authorization|bearer|credential|destination|password|secret|token)(?:$|[^A-Za-z0-9])/i
|
|
162
|
+
.test(normalized);
|
|
163
|
+
}
|
|
164
|
+
|
|
99
165
|
function hasControlCharacter(value: string): boolean {
|
|
100
166
|
for (let index = 0; index < value.length; index += 1) {
|
|
101
167
|
const code = value.charCodeAt(index);
|
|
@@ -10,6 +10,7 @@ import { normalizeCompactDecisionEquivalence } from
|
|
|
10
10
|
export interface CompactDecisionNode {
|
|
11
11
|
key?: string;
|
|
12
12
|
decisionTarget?: string;
|
|
13
|
+
projectedIdentity?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
function resolvedNode(
|
|
@@ -40,7 +41,10 @@ function sameCanonicalNode(
|
|
|
40
41
|
left: CompactDecisionNode | undefined,
|
|
41
42
|
right: CompactDecisionNode,
|
|
42
43
|
): boolean {
|
|
43
|
-
|
|
44
|
+
if (left?.key === undefined) return false;
|
|
45
|
+
return left.projectedIdentity === true
|
|
46
|
+
? left.key === right.decisionTarget
|
|
47
|
+
: left.key === right.key;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
function persistedCanonical(
|
|
@@ -49,7 +53,7 @@ function persistedCanonical(
|
|
|
49
53
|
target: CompactDecisionNode,
|
|
50
54
|
): boolean {
|
|
51
55
|
if (!sameCanonicalNode(persisted, target)) return false;
|
|
52
|
-
return effective?.key === undefined || effective
|
|
56
|
+
return effective?.key === undefined || sameCanonicalNode(effective, target);
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
export function projectObservationDecision(
|
|
@@ -71,6 +75,7 @@ export function projectObservationDecision(
|
|
|
71
75
|
target.decisionTarget,
|
|
72
76
|
sameCanonicalNode(effective, target),
|
|
73
77
|
persistedCanonical(persisted, effective, target),
|
|
78
|
+
source?.persistedResolutionStatus,
|
|
74
79
|
);
|
|
75
80
|
return decision;
|
|
76
81
|
}
|
|
@@ -150,6 +150,7 @@ function unresolvedOperationStart(
|
|
|
150
150
|
severity: 'warning',
|
|
151
151
|
code: 'trace_start_implementation_unresolved',
|
|
152
152
|
message: 'Indexed operation matched but no implementation candidate exists',
|
|
153
|
+
selectorKind: 'operation',
|
|
153
154
|
resolutionStage: 'implementation',
|
|
154
155
|
resolutionStatus: 'operation_without_implementation',
|
|
155
156
|
}],
|
|
@@ -17,10 +17,7 @@ import {
|
|
|
17
17
|
TraversalScopeScheduler,
|
|
18
18
|
type TraversalScopeState,
|
|
19
19
|
} from './010-traversal-scope.js';
|
|
20
|
-
import {
|
|
21
|
-
planEventSubscriberTransitions,
|
|
22
|
-
type PlannedEventSubscriberTransition,
|
|
23
|
-
} from './011-event-subscriber-traversal.js';
|
|
20
|
+
import type { PlannedEventSubscriberTransition } from './011-event-subscriber-traversal.js';
|
|
24
21
|
import {
|
|
25
22
|
graphForCalls,
|
|
26
23
|
operationNode,
|
|
@@ -36,7 +33,6 @@ import {
|
|
|
36
33
|
import type { CompactSemanticEndpoint } from './014-compact-contract.js';
|
|
37
34
|
import { TraceEdgeRecorder } from './015-trace-edge-recorder.js';
|
|
38
35
|
import {
|
|
39
|
-
contextForSymbolCall,
|
|
40
36
|
knownBindingsForCalls,
|
|
41
37
|
knownBindingsForScope,
|
|
42
38
|
parseTraceEvidence,
|
|
@@ -53,6 +49,8 @@ import {
|
|
|
53
49
|
import { outboundScopeSymbolIds } from './023-nested-event-scopes.js';
|
|
54
50
|
import type { ImplementationHintOptions } from './025-trace-implementation-scope.js';
|
|
55
51
|
import { processOperationTarget } from './028-trace-operation-execution.js';
|
|
52
|
+
import { runtimeEventResolution, runtimeEventSubscriberPlans } from './030-event-runtime-resolution.js';
|
|
53
|
+
import { planLocalCallExpansion } from './031-local-call-expansion.js';
|
|
56
54
|
|
|
57
55
|
export interface CallRow extends Record<string, unknown> {
|
|
58
56
|
id: number;
|
|
@@ -226,20 +224,10 @@ function processLocalCall(
|
|
|
226
224
|
bindings: Map<string, ContextBinding>,
|
|
227
225
|
row: Record<string, unknown>,
|
|
228
226
|
): void {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
const repoId = Number(row.calleeRepoId);
|
|
234
|
-
const context = contextForSymbolCall(runtime.db, row, bindings);
|
|
235
|
-
const scheduling = runtime.scheduler.schedule({
|
|
236
|
-
workspaceId: runtime.workspaceId,
|
|
237
|
-
repoId,
|
|
238
|
-
files,
|
|
239
|
-
symbolIds: symbols,
|
|
240
|
-
context,
|
|
241
|
-
}, current.state);
|
|
242
|
-
const node = symbolNode(runtime.db, symbolId);
|
|
227
|
+
const symbolId = row.callee_symbol_id
|
|
228
|
+
? Number(row.callee_symbol_id) : undefined;
|
|
229
|
+
const node = symbolId === undefined
|
|
230
|
+
? undefined : symbolNode(runtime.db, symbolId);
|
|
243
231
|
if (node) runtime.nodes.set(String(node.id), node);
|
|
244
232
|
const evidence = localCallEvidence(row, node);
|
|
245
233
|
const unresolvedReason = localCallUnresolvedReason(row);
|
|
@@ -247,6 +235,12 @@ function processLocalCall(
|
|
|
247
235
|
const target = recordLocalCallObservation(runtime.recorder, edge, {
|
|
248
236
|
symbolCall: row, evidence, unresolvedReason,
|
|
249
237
|
});
|
|
238
|
+
if (symbolId === undefined || row.status !== 'resolved' || !node) return;
|
|
239
|
+
const { repoId, files, symbols, context, scheduling } =
|
|
240
|
+
planLocalCallExpansion(
|
|
241
|
+
runtime.db, runtime.scheduler, runtime.workspaceId, current.state,
|
|
242
|
+
row, bindings, symbolId,
|
|
243
|
+
);
|
|
250
244
|
if (scheduling.kind === 'cycle')
|
|
251
245
|
recordLocalCycle(runtime, current, row, target, scheduling.state,
|
|
252
246
|
repoId, files, symbols);
|
|
@@ -291,7 +285,8 @@ function localTraceEdge(
|
|
|
291
285
|
type: 'local_symbol_call',
|
|
292
286
|
from: String(row.callee_expression),
|
|
293
287
|
to: node?.label
|
|
294
|
-
? String(node.label)
|
|
288
|
+
? String(node.label)
|
|
289
|
+
: `${String(row.status)}:${String(row.callee_expression)}`,
|
|
295
290
|
evidence,
|
|
296
291
|
confidence: Number(row.confidence ?? 0.8),
|
|
297
292
|
unresolvedReason,
|
|
@@ -390,11 +385,12 @@ function recordEffectiveOutbound(
|
|
|
390
385
|
): EffectiveOutbound {
|
|
391
386
|
const persisted = parseTraceEvidence(row.evidence_json);
|
|
392
387
|
const raw = baseTraceEvidence(row, call, persisted, contextual.evidence);
|
|
393
|
-
const effective =
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
388
|
+
const effective = runtimeEventResolution(row, raw, runtime.options.vars)
|
|
389
|
+
?? runtimeResolution(runtime.db, row, raw, {
|
|
390
|
+
vars: runtime.options.vars,
|
|
391
|
+
dynamicMode: runtime.options.dynamicMode ?? 'strict',
|
|
392
|
+
maxDynamicCandidates: runtime.options.maxDynamicCandidates,
|
|
393
|
+
}, call.workspaceId, contextual.state);
|
|
398
394
|
const target = `${effective.row.to_kind}:${effective.row.to_id}`;
|
|
399
395
|
const operation = effective.row.to_kind === 'operation'
|
|
400
396
|
? operationNode(runtime.db, effective.row.to_id) : undefined;
|
|
@@ -461,15 +457,19 @@ function processEventTransitions(
|
|
|
461
457
|
): void {
|
|
462
458
|
if (!runtime.options.includeAsync || call.call_type !== 'async_emit'
|
|
463
459
|
|| effective.row.edge_type !== 'HANDLER_EMITS_EVENT'
|
|
464
|
-
||
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
460
|
+
|| effective.row.to_kind !== 'event') return;
|
|
461
|
+
const planned = runtimeEventSubscriberPlans(
|
|
462
|
+
runtime.db, {
|
|
463
|
+
workspaceId: runtime.workspaceId ?? call.workspaceId,
|
|
464
|
+
graphGeneration: call.graphGeneration,
|
|
465
|
+
eventName: effective.row.to_id,
|
|
466
|
+
vars: runtime.options.vars ?? {},
|
|
467
|
+
}, runtime.scheduler, current.state, current.depth, runtime.maxDepth,
|
|
468
|
+
);
|
|
469
|
+
if (planned.diagnostic) runtime.diagnostics.push(planned.diagnostic);
|
|
470
|
+
for (const plan of planned.plans)
|
|
471
471
|
recordEventTransition(runtime, current, plan,
|
|
472
|
-
effective.semanticWorkspaceId, plans.length);
|
|
472
|
+
effective.semanticWorkspaceId, planned.plans.length);
|
|
473
473
|
}
|
|
474
474
|
|
|
475
475
|
function recordEventTransition(
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
substituteVariables,
|
|
3
|
+
type RuntimeSubstitution,
|
|
4
|
+
} from '../linker/dynamic-edge-resolver.js';
|
|
5
|
+
import type { Db } from '../db/connection.js';
|
|
6
|
+
import {
|
|
7
|
+
eventSubscriberMissingVariables,
|
|
8
|
+
planEventSubscriberTransitions,
|
|
9
|
+
type EventSubscriberTransitionQuery,
|
|
10
|
+
type PlannedEventSubscriberTransition,
|
|
11
|
+
} from './011-event-subscriber-traversal.js';
|
|
12
|
+
import type {
|
|
13
|
+
TraversalScopeScheduler,
|
|
14
|
+
TraversalScopeState,
|
|
15
|
+
} from './010-traversal-scope.js';
|
|
16
|
+
import type { TraceGraphRow } from './evidence.js';
|
|
17
|
+
|
|
18
|
+
export interface EventRuntimeResolution {
|
|
19
|
+
row: TraceGraphRow;
|
|
20
|
+
evidence: Record<string, unknown>;
|
|
21
|
+
unresolvedReason?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface EventSubscriberRuntimePlan {
|
|
25
|
+
plans: PlannedEventSubscriberTransition[];
|
|
26
|
+
diagnostic?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
30
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
31
|
+
? value as Record<string, unknown> : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function eventTemplate(evidence: Record<string, unknown>): string | undefined {
|
|
35
|
+
const resolution = record(evidence.eventTemplateResolution);
|
|
36
|
+
return typeof resolution?.original === 'string'
|
|
37
|
+
? resolution.original : undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function missingReason(substitution: RuntimeSubstitution): string {
|
|
41
|
+
return `Dynamic target requires runtime variable overrides: ${
|
|
42
|
+
substitution.missing.join(', ')}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function missingSubscriberDiagnostic(
|
|
46
|
+
missing: readonly string[],
|
|
47
|
+
): Record<string, unknown> | undefined {
|
|
48
|
+
if (missing.length === 0) return undefined;
|
|
49
|
+
return {
|
|
50
|
+
severity: 'warning',
|
|
51
|
+
code: 'trace_runtime_variables_missing',
|
|
52
|
+
message: `Runtime variables are required to resolve dynamic event subscribers: ${
|
|
53
|
+
missing.join(', ')}`,
|
|
54
|
+
missingVariables: missing,
|
|
55
|
+
suggestions: missing.map((key) => `--var ${key}=<value>`),
|
|
56
|
+
source: 'event_subscription',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function runtimeEventSubscriberPlans(
|
|
61
|
+
db: Db,
|
|
62
|
+
query: EventSubscriberTransitionQuery,
|
|
63
|
+
scheduler: TraversalScopeScheduler,
|
|
64
|
+
parent: TraversalScopeState,
|
|
65
|
+
depth: number,
|
|
66
|
+
maxDepth: number,
|
|
67
|
+
): EventSubscriberRuntimePlan {
|
|
68
|
+
const missing = eventSubscriberMissingVariables(db, query);
|
|
69
|
+
return {
|
|
70
|
+
plans: planEventSubscriberTransitions(
|
|
71
|
+
db, query, scheduler, parent, depth, maxDepth,
|
|
72
|
+
),
|
|
73
|
+
diagnostic: missingSubscriberDiagnostic(missing),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function eventEvidence(
|
|
78
|
+
evidence: Record<string, unknown>,
|
|
79
|
+
substitution: RuntimeSubstitution,
|
|
80
|
+
row: TraceGraphRow,
|
|
81
|
+
unresolvedReason?: string,
|
|
82
|
+
): Record<string, unknown> {
|
|
83
|
+
const effectiveResolution = {
|
|
84
|
+
status: unresolvedReason ? 'dynamic' : 'terminal',
|
|
85
|
+
targetKind: row.to_kind,
|
|
86
|
+
targetId: row.to_id,
|
|
87
|
+
confidence: row.confidence,
|
|
88
|
+
unresolvedReason,
|
|
89
|
+
edgeType: row.edge_type,
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
...evidence,
|
|
93
|
+
runtimeSubstitutions: { eventName: substitution },
|
|
94
|
+
suppliedRuntimeVariables: Object.fromEntries(
|
|
95
|
+
substitution.supplied.map((key) => [key, true]),
|
|
96
|
+
),
|
|
97
|
+
...(substitution.supplied.length > 0
|
|
98
|
+
? { runtimeVariablesApplied: true } : {}),
|
|
99
|
+
...(substitution.missing.length > 0
|
|
100
|
+
? { missingRuntimeVariables: substitution.missing } : {}),
|
|
101
|
+
effectiveResolution,
|
|
102
|
+
linker: {
|
|
103
|
+
status: effectiveResolution.status,
|
|
104
|
+
confidence: row.confidence,
|
|
105
|
+
reason: unresolvedReason,
|
|
106
|
+
edgeType: row.edge_type,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function runtimeEventRow(
|
|
112
|
+
row: TraceGraphRow,
|
|
113
|
+
evidence: Record<string, unknown>,
|
|
114
|
+
substitution: RuntimeSubstitution,
|
|
115
|
+
template: string,
|
|
116
|
+
): TraceGraphRow {
|
|
117
|
+
const missing = substitution.missing.length > 0;
|
|
118
|
+
const eventName = substitution.effective ?? template;
|
|
119
|
+
const callType = evidence.callType;
|
|
120
|
+
const edgeType = callType === 'async_emit'
|
|
121
|
+
? 'HANDLER_EMITS_EVENT' : 'EVENT_CONSUMED_BY_HANDLER';
|
|
122
|
+
return {
|
|
123
|
+
...row,
|
|
124
|
+
edge_type: missing ? 'DYNAMIC_EDGE_CANDIDATE' : edgeType,
|
|
125
|
+
status: missing ? 'dynamic' : 'terminal',
|
|
126
|
+
to_kind: missing ? 'event_candidate' : 'event',
|
|
127
|
+
to_id: missing ? `Event: ${eventName}` : eventName,
|
|
128
|
+
unresolved_reason: missing ? missingReason(substitution) : undefined,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function runtimeEventResolution(
|
|
133
|
+
row: TraceGraphRow,
|
|
134
|
+
evidence: Record<string, unknown>,
|
|
135
|
+
variables: Record<string, string> | undefined,
|
|
136
|
+
): EventRuntimeResolution | undefined {
|
|
137
|
+
const template = eventTemplate(evidence);
|
|
138
|
+
if (!template || row.to_kind === 'event' && variables === undefined)
|
|
139
|
+
return undefined;
|
|
140
|
+
const substitution = substituteVariables(template, variables ?? {});
|
|
141
|
+
const effectiveRow = runtimeEventRow(
|
|
142
|
+
row, evidence, substitution, template,
|
|
143
|
+
);
|
|
144
|
+
return {
|
|
145
|
+
row: effectiveRow,
|
|
146
|
+
evidence: eventEvidence(
|
|
147
|
+
evidence, substitution, effectiveRow, effectiveRow.unresolved_reason,
|
|
148
|
+
),
|
|
149
|
+
unresolvedReason: effectiveRow.unresolved_reason,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Db } from '../db/connection.js';
|
|
2
|
+
import {
|
|
3
|
+
type ContextBinding,
|
|
4
|
+
} from './008-contextual-runtime-state.js';
|
|
5
|
+
import {
|
|
6
|
+
type TraversalScheduleDecision,
|
|
7
|
+
type TraversalScopeScheduler,
|
|
8
|
+
type TraversalScopeState,
|
|
9
|
+
} from './010-traversal-scope.js';
|
|
10
|
+
import { contextForSymbolCall } from './017-trace-context.js';
|
|
11
|
+
|
|
12
|
+
export interface LocalCallExpansion {
|
|
13
|
+
repoId: number;
|
|
14
|
+
files: Set<string>;
|
|
15
|
+
symbols: Set<number>;
|
|
16
|
+
context: Map<string, ContextBinding>;
|
|
17
|
+
scheduling: TraversalScheduleDecision;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function planLocalCallExpansion(
|
|
21
|
+
db: Db,
|
|
22
|
+
scheduler: TraversalScopeScheduler,
|
|
23
|
+
workspaceId: number | undefined,
|
|
24
|
+
parent: TraversalScopeState,
|
|
25
|
+
row: Record<string, unknown>,
|
|
26
|
+
bindings: Map<string, ContextBinding>,
|
|
27
|
+
symbolId: number,
|
|
28
|
+
): LocalCallExpansion {
|
|
29
|
+
const symbols = new Set([symbolId]);
|
|
30
|
+
const files = new Set([String(row.calleeFile)]);
|
|
31
|
+
const repoId = Number(row.calleeRepoId);
|
|
32
|
+
const context = contextForSymbolCall(db, row, bindings);
|
|
33
|
+
const scheduling = scheduler.schedule({
|
|
34
|
+
workspaceId, repoId, files, symbolIds: symbols, context,
|
|
35
|
+
}, parent);
|
|
36
|
+
return { repoId, files, symbols, context, scheduling };
|
|
37
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ImplementationHint } from '../types.js';
|
|
2
2
|
import { projectBounded } from '../utils/000-bounded-projection.js';
|
|
3
|
+
import { compareBinary } from './010-traversal-scope.js';
|
|
3
4
|
|
|
4
5
|
interface Candidate {
|
|
5
6
|
accepted?: boolean;
|
|
@@ -106,6 +107,7 @@ export function implementationHintSuggestions(rawEvidence: Record<string, unknow
|
|
|
106
107
|
|
|
107
108
|
export function implementationHintSuggestionProjection(
|
|
108
109
|
rawEvidence: Record<string, unknown>,
|
|
110
|
+
limit?: number,
|
|
109
111
|
): ImplementationHintSuggestionProjection {
|
|
110
112
|
const evidence = asEvidence(rawEvidence);
|
|
111
113
|
const accepted = (evidence.candidates ?? []).filter((candidate) => candidate.accepted);
|
|
@@ -119,7 +121,7 @@ export function implementationHintSuggestionProjection(
|
|
|
119
121
|
}
|
|
120
122
|
const repos = selectableRepositories(accepted);
|
|
121
123
|
const repositoryProjection = projectBounded(
|
|
122
|
-
repos,
|
|
124
|
+
repos, compareBinary, limit,
|
|
123
125
|
);
|
|
124
126
|
const suggestions = accepted
|
|
125
127
|
.flatMap((candidate) => {
|
|
@@ -142,7 +144,7 @@ export function implementationHintSuggestionProjection(
|
|
|
142
144
|
cli: `--implementation-hint ${hintString(hint)}`,
|
|
143
145
|
}];
|
|
144
146
|
});
|
|
145
|
-
const projection = projectBounded(suggestions, compareSuggestion);
|
|
147
|
+
const projection = projectBounded(suggestions, compareSuggestion, limit);
|
|
146
148
|
return {
|
|
147
149
|
suggestions: projection.items,
|
|
148
150
|
suggestionCount: projection.totalCount,
|
|
@@ -170,15 +172,16 @@ function projectedSuggestions(value: unknown): ImplementationHintSuggestionProje
|
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
function compareHints(left: ImplementationHint, right: ImplementationHint): number {
|
|
173
|
-
return hintString(left)
|
|
175
|
+
return compareBinary(hintString(left), hintString(right));
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
function compareSuggestion(
|
|
177
179
|
left: Record<string, unknown>,
|
|
178
180
|
right: Record<string, unknown>,
|
|
179
181
|
): number {
|
|
180
|
-
return String(left.cli ?? '')
|
|
181
|
-
||
|
|
182
|
+
return compareBinary(String(left.cli ?? ''), String(right.cli ?? ''))
|
|
183
|
+
|| compareBinary(
|
|
184
|
+
String(left.implementationRepo ?? ''),
|
|
182
185
|
String(right.implementationRepo ?? ''),
|
|
183
186
|
);
|
|
184
187
|
}
|
|
@@ -202,7 +205,7 @@ function selectableRepositories(candidates: Candidate[]): string[] {
|
|
|
202
205
|
const repos = new Set(candidates.flatMap((candidate) => candidate.handlerPackage?.name ? [candidate.handlerPackage.name] : []));
|
|
203
206
|
return [...repos]
|
|
204
207
|
.filter((repo) => candidates.filter((candidate) => candidateMatchesRepo(candidate, repo)).length === 1)
|
|
205
|
-
.sort();
|
|
208
|
+
.sort(compareBinary);
|
|
206
209
|
}
|
|
207
210
|
|
|
208
211
|
function assignHintField(hint: Partial<ImplementationHint>, key: string, value: string): void {
|
package/src/trace/selectors.ts
CHANGED
|
@@ -550,6 +550,7 @@ export function ambiguousStartDiagnostic(
|
|
|
550
550
|
severity: 'warning',
|
|
551
551
|
code: 'trace_start_ambiguous',
|
|
552
552
|
message,
|
|
553
|
+
selectorKind: 'operation',
|
|
553
554
|
normalizedSelectorValue: requested,
|
|
554
555
|
resolutionStage: 'operation',
|
|
555
556
|
resolutionStatus: 'ambiguous_operation',
|
package/src/types.ts
CHANGED
|
@@ -203,7 +203,8 @@ export interface ServiceBindingReference {
|
|
|
203
203
|
bindingSiteEndOffset?: number;
|
|
204
204
|
resolutionStrategy?: 'lexical_declaration'
|
|
205
205
|
| 'lexical_alias_declaration'
|
|
206
|
-
| 'deterministic_reaching_assignment'
|
|
206
|
+
| 'deterministic_reaching_assignment'
|
|
207
|
+
| 'single_hop_helper_return';
|
|
207
208
|
lexicalScopeChain?: LexicalScopeFact[];
|
|
208
209
|
bindingScopeIndex?: number;
|
|
209
210
|
scopeChainTotal: number;
|
package/src/version.ts
CHANGED