@saptools/service-flow 0.1.67 → 0.1.68
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 +20 -8
- package/TECHNICAL-NOTE.md +27 -0
- package/dist/{chunk-ZQABU7MR.js → chunk-AEM4JY22.js} +12676 -6714
- package/dist/chunk-AEM4JY22.js.map +1 -0
- package/dist/cli.js +2325 -413
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +61 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/003-doctor-package-resolution.ts +68 -0
- package/src/cli/doctor.ts +25 -14
- package/src/db/000-call-fact-repository.ts +475 -342
- 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 +698 -0
- package/src/db/004-package-target-invalidation.ts +173 -0
- package/src/db/005-schema-structure.ts +201 -0
- package/src/db/006-relative-symbol-resolution.ts +443 -0
- package/src/db/007-package-fact-semantics.ts +573 -0
- package/src/db/008-relative-fact-semantics.ts +207 -0
- package/src/db/009-binding-fact-semantics.ts +347 -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 +264 -0
- package/src/db/migrations.ts +16 -3
- package/src/db/repositories.ts +113 -6
- package/src/db/schema.ts +4 -2
- package/src/index.ts +12 -0
- package/src/indexer/repository-indexer.ts +72 -11
- package/src/indexer/workspace-indexer.ts +123 -32
- package/src/linker/003-package-import-symbol-resolver.ts +363 -131
- package/src/linker/004-event-subscription-handler-linker.ts +2 -0
- package/src/linker/005-odata-path-structure.ts +371 -0
- package/src/linker/cross-repo-linker.ts +2 -1
- package/src/linker/odata-path-normalizer.ts +273 -180
- package/src/linker/service-resolver.ts +197 -77
- 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 +343 -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 +240 -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 +152 -0
- package/src/parsers/operation-path-analysis.ts +6 -1
- package/src/parsers/outbound-call-parser.ts +19 -6
- 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 +482 -353
- package/src/trace/002-trace-diagnostics.ts +36 -1
- package/src/trace/016-compact-projector.ts +16 -17
- package/src/trace/020-compact-field-projection.ts +48 -37
- package/src/trace/021-compact-decision-normalization.ts +105 -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 +76 -0
- package/src/trace/025-trace-implementation-scope.ts +123 -0
- package/src/trace/026-trace-start-scope.ts +335 -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/trace-engine.ts +122 -624
- package/src/types.ts +56 -0
- package/src/utils/001-placeholders.ts +188 -10
- package/src/version.ts +1 -1
- package/dist/chunk-ZQABU7MR.js.map +0 -1
|
@@ -22,6 +22,17 @@ export interface OperationResolution {
|
|
|
22
22
|
candidates: OperationTarget[];
|
|
23
23
|
reasons: string[];
|
|
24
24
|
}
|
|
25
|
+
interface OperationSignals {
|
|
26
|
+
servicePath?: string;
|
|
27
|
+
alias?: string;
|
|
28
|
+
destination?: string;
|
|
29
|
+
operationPath?: string;
|
|
30
|
+
serviceName?: string;
|
|
31
|
+
repoId?: number;
|
|
32
|
+
hasExplicitOverride?: boolean;
|
|
33
|
+
isDynamic?: boolean;
|
|
34
|
+
localServiceLookup?: string;
|
|
35
|
+
}
|
|
25
36
|
function rows(
|
|
26
37
|
db: Db,
|
|
27
38
|
operationPath: string,
|
|
@@ -60,31 +71,30 @@ function operationMatches(candidate: OperationTarget, operationPath: string | un
|
|
|
60
71
|
}
|
|
61
72
|
export function resolveOperation(
|
|
62
73
|
db: Db,
|
|
63
|
-
signals:
|
|
64
|
-
servicePath?: string;
|
|
65
|
-
alias?: string;
|
|
66
|
-
destination?: string;
|
|
67
|
-
operationPath?: string;
|
|
68
|
-
serviceName?: string;
|
|
69
|
-
repoId?: number;
|
|
70
|
-
hasExplicitOverride?: boolean;
|
|
71
|
-
isDynamic?: boolean;
|
|
72
|
-
localServiceLookup?: string;
|
|
73
|
-
},
|
|
74
|
+
signals: OperationSignals,
|
|
74
75
|
workspaceId?: number,
|
|
75
76
|
): OperationResolution {
|
|
77
|
+
const early = earlyOperationResolution(db, signals, workspaceId);
|
|
78
|
+
if (early) return early;
|
|
79
|
+
const selected = selectOperationCandidates(db, signals, workspaceId);
|
|
80
|
+
if (!Array.isArray(selected)) return selected;
|
|
81
|
+
const candidates = scoreOperationCandidates(selected, signals);
|
|
82
|
+
return finalOperationResolution(candidates, signals);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function earlyOperationResolution(
|
|
86
|
+
db: Db,
|
|
87
|
+
signals: OperationSignals,
|
|
88
|
+
workspaceId: number | undefined,
|
|
89
|
+
): OperationResolution | undefined {
|
|
76
90
|
const missing = [signals.servicePath, signals.alias, signals.destination, signals.operationPath]
|
|
77
91
|
.flatMap(extractPlaceholderKeys);
|
|
78
92
|
if (missing.length > 0)
|
|
79
93
|
return {
|
|
80
94
|
status: 'dynamic',
|
|
81
|
-
candidates:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
score: 0.2,
|
|
85
|
-
reasons: ['operation_path_match'],
|
|
86
|
-
}))
|
|
87
|
-
: [],
|
|
95
|
+
candidates: dynamicOperationCandidates(
|
|
96
|
+
db, signals.operationPath, workspaceId,
|
|
97
|
+
),
|
|
88
98
|
reasons: [...new Set(missing)].map((name) => `missing_variable:${name}`),
|
|
89
99
|
};
|
|
90
100
|
if (!signals.operationPath)
|
|
@@ -93,72 +103,184 @@ export function resolveOperation(
|
|
|
93
103
|
candidates: [],
|
|
94
104
|
reasons: ['missing_operation_path'],
|
|
95
105
|
};
|
|
96
|
-
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function dynamicOperationCandidates(
|
|
110
|
+
db: Db,
|
|
111
|
+
operationPath: string | undefined,
|
|
112
|
+
workspaceId: number | undefined,
|
|
113
|
+
): OperationTarget[] {
|
|
114
|
+
if (!operationPath || extractPlaceholderKeys(operationPath).length > 0)
|
|
115
|
+
return [];
|
|
116
|
+
return rows(db, operationPath, workspaceId).map((candidate) => ({
|
|
117
|
+
...candidate,
|
|
118
|
+
score: 0.2,
|
|
119
|
+
reasons: ['operation_path_match'],
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function selectOperationCandidates(
|
|
124
|
+
db: Db,
|
|
125
|
+
signals: OperationSignals,
|
|
126
|
+
workspaceId?: number,
|
|
127
|
+
): OperationTarget[] | OperationResolution {
|
|
128
|
+
const operationPath = signals.operationPath ?? '';
|
|
129
|
+
const allCandidates = rows(db, operationPath, workspaceId).map((c) => ({
|
|
97
130
|
...c,
|
|
98
131
|
score: 0.2,
|
|
99
132
|
reasons: ['operation_path_match'],
|
|
100
133
|
}));
|
|
101
134
|
let candidates = allCandidates.filter((c) => matchesLocalRepo(db, c.operationId, signals.repoId));
|
|
102
|
-
if (candidates.length === 0 && signals.repoId !== undefined
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
135
|
+
if (candidates.length === 0 && signals.repoId !== undefined
|
|
136
|
+
&& signals.serviceName)
|
|
137
|
+
candidates = implementationContextCandidates(
|
|
138
|
+
db, allCandidates, signals.repoId, signals.serviceName,
|
|
139
|
+
);
|
|
140
|
+
if (candidates.length === 0 && signals.repoId !== undefined
|
|
141
|
+
&& signals.serviceName)
|
|
142
|
+
return missingOwnedCandidate(allCandidates, signals.serviceName);
|
|
111
143
|
if (candidates.length === 0)
|
|
112
144
|
return {
|
|
113
145
|
status: 'unresolved',
|
|
114
146
|
candidates: [],
|
|
115
147
|
reasons: ['no_operation_candidates'],
|
|
116
148
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
149
|
+
return candidates;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function missingOwnedCandidate(
|
|
153
|
+
candidates: OperationTarget[],
|
|
154
|
+
serviceName: string,
|
|
155
|
+
): OperationResolution {
|
|
156
|
+
const matching = candidates.filter((candidate) =>
|
|
157
|
+
serviceMatches(candidate, serviceName));
|
|
158
|
+
return {
|
|
159
|
+
status: 'unresolved',
|
|
160
|
+
candidates: matching,
|
|
161
|
+
reasons: matching.length > 0
|
|
162
|
+
? ['local_service_candidate_without_caller_ownership']
|
|
163
|
+
: ['no_operation_candidates'],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function scoreOperationCandidates(
|
|
168
|
+
candidates: OperationTarget[],
|
|
169
|
+
signals: OperationSignals,
|
|
170
|
+
): OperationTarget[] {
|
|
124
171
|
for (const c of candidates) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
c.score -= 0.1;
|
|
131
|
-
c.reasons.push('service_path_mismatch');
|
|
132
|
-
}
|
|
133
|
-
if (signals.serviceName) {
|
|
134
|
-
const simple = signals.serviceName.split('.').at(-1) ?? signals.serviceName;
|
|
135
|
-
if (c.qualifiedName === signals.serviceName) {
|
|
136
|
-
c.score += 0.8;
|
|
137
|
-
c.reasons.push('exact_local_qualified_service_name');
|
|
138
|
-
} else if (c.serviceName === signals.serviceName || c.serviceName === simple) {
|
|
139
|
-
c.score += 0.75;
|
|
140
|
-
c.reasons.push('exact_local_simple_service_name');
|
|
141
|
-
} else if (c.servicePath === signals.serviceName || c.servicePath === `/${signals.serviceName}` || c.servicePath === `/${simple}`) {
|
|
142
|
-
c.score += 0.7;
|
|
143
|
-
c.reasons.push('exact_local_service_path');
|
|
144
|
-
} else if (c.servicePath.endsWith(`/${simple}`)) {
|
|
145
|
-
c.score += candidates.filter((candidate) => candidate.servicePath.endsWith(`/${simple}`)).length === 1 ? 0.65 : 0.2;
|
|
146
|
-
c.reasons.push('suffix_local_service_path');
|
|
147
|
-
} else c.reasons.push('local_service_name_mismatch');
|
|
148
|
-
}
|
|
149
|
-
if (signals.hasExplicitOverride) {
|
|
150
|
-
c.score += 0.2;
|
|
151
|
-
c.reasons.push(signals.repoId !== undefined ? 'explicit_local_service_call' : 'explicit_dynamic_override');
|
|
152
|
-
}
|
|
153
|
-
if (signals.repoId !== undefined && candidates.length === 1 && signals.serviceName && c.reasons.includes('local_service_name_mismatch') && operationMatches(c, signals.operationPath)) {
|
|
154
|
-
c.score = Math.max(c.score, 0.9);
|
|
155
|
-
c.reasons.push('same_repo_unique_operation_path_with_lookup_mismatch');
|
|
156
|
-
}
|
|
172
|
+
applyServicePathScore(c, signals.servicePath);
|
|
173
|
+
applyServiceNameScore(c, signals.serviceName, candidates);
|
|
174
|
+
applyOverrideScore(c, signals);
|
|
175
|
+
applyUniquePathRecovery(c, candidates, signals);
|
|
176
|
+
c.score = Math.max(0, Math.min(1, c.score));
|
|
157
177
|
}
|
|
158
|
-
for (const c of candidates) c.score = Math.max(0, Math.min(1, c.score));
|
|
159
178
|
candidates.sort(
|
|
160
|
-
(a, b) => b.score - a.score || a.repoName
|
|
179
|
+
(a, b) => b.score - a.score || compareBinary(a.repoName, b.repoName),
|
|
180
|
+
);
|
|
181
|
+
return candidates;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function applyServicePathScore(
|
|
185
|
+
candidate: OperationTarget,
|
|
186
|
+
servicePath: string | undefined,
|
|
187
|
+
): void {
|
|
188
|
+
if (!servicePath) return;
|
|
189
|
+
const exact = candidate.servicePath === servicePath;
|
|
190
|
+
candidate.score += exact ? 0.75 : -0.1;
|
|
191
|
+
candidate.reasons.push(exact ? 'exact_service_path' : 'service_path_mismatch');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function serviceNameScore(
|
|
195
|
+
candidate: OperationTarget,
|
|
196
|
+
serviceName: string,
|
|
197
|
+
candidates: readonly OperationTarget[],
|
|
198
|
+
): { score: number; reason: string } {
|
|
199
|
+
const simple = serviceName.split('.').at(-1) ?? serviceName;
|
|
200
|
+
if (candidate.qualifiedName === serviceName)
|
|
201
|
+
return { score: 0.8, reason: 'exact_local_qualified_service_name' };
|
|
202
|
+
if (candidate.serviceName === serviceName || candidate.serviceName === simple)
|
|
203
|
+
return { score: 0.75, reason: 'exact_local_simple_service_name' };
|
|
204
|
+
if ([serviceName, `/${serviceName}`, `/${simple}`]
|
|
205
|
+
.includes(candidate.servicePath))
|
|
206
|
+
return { score: 0.7, reason: 'exact_local_service_path' };
|
|
207
|
+
if (candidate.servicePath.endsWith(`/${simple}`)) return {
|
|
208
|
+
score: candidates.filter((item) =>
|
|
209
|
+
item.servicePath.endsWith(`/${simple}`)).length === 1 ? 0.65 : 0.2,
|
|
210
|
+
reason: 'suffix_local_service_path',
|
|
211
|
+
};
|
|
212
|
+
return { score: 0, reason: 'local_service_name_mismatch' };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function applyServiceNameScore(
|
|
216
|
+
candidate: OperationTarget,
|
|
217
|
+
serviceName: string | undefined,
|
|
218
|
+
candidates: readonly OperationTarget[],
|
|
219
|
+
): void {
|
|
220
|
+
if (!serviceName) return;
|
|
221
|
+
const signal = serviceNameScore(candidate, serviceName, candidates);
|
|
222
|
+
candidate.score += signal.score;
|
|
223
|
+
candidate.reasons.push(signal.reason);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function applyOverrideScore(
|
|
227
|
+
candidate: OperationTarget,
|
|
228
|
+
signals: OperationSignals,
|
|
229
|
+
): void {
|
|
230
|
+
if (!signals.hasExplicitOverride) return;
|
|
231
|
+
candidate.score += 0.2;
|
|
232
|
+
candidate.reasons.push(signals.repoId !== undefined
|
|
233
|
+
? 'explicit_local_service_call' : 'explicit_dynamic_override');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function applyUniquePathRecovery(
|
|
237
|
+
candidate: OperationTarget,
|
|
238
|
+
candidates: readonly OperationTarget[],
|
|
239
|
+
signals: OperationSignals,
|
|
240
|
+
): void {
|
|
241
|
+
if (signals.repoId === undefined || candidates.length !== 1
|
|
242
|
+
|| !signals.serviceName
|
|
243
|
+
|| !candidate.reasons.includes('local_service_name_mismatch')
|
|
244
|
+
|| !operationMatches(candidate, signals.operationPath)) return;
|
|
245
|
+
candidate.score = Math.max(candidate.score, 0.9);
|
|
246
|
+
candidate.reasons.push(
|
|
247
|
+
'same_repo_unique_operation_path_with_lookup_mismatch',
|
|
161
248
|
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function strongSignal(signals: OperationSignals): boolean {
|
|
252
|
+
return Boolean(signals.servicePath || signals.serviceName || signals.alias
|
|
253
|
+
|| signals.destination || signals.hasExplicitOverride);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function targetSignalMatches(
|
|
257
|
+
candidate: OperationTarget,
|
|
258
|
+
signals: OperationSignals,
|
|
259
|
+
): boolean {
|
|
260
|
+
if (candidate.servicePath === signals.servicePath) return true;
|
|
261
|
+
if (!signals.serviceName) return false;
|
|
262
|
+
return !candidate.reasons.includes('local_service_name_mismatch')
|
|
263
|
+
|| candidate.reasons.includes(
|
|
264
|
+
'same_repo_unique_operation_path_with_lookup_mismatch',
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function bestCandidateResolves(
|
|
269
|
+
best: OperationTarget | undefined,
|
|
270
|
+
second: OperationTarget | undefined,
|
|
271
|
+
signals: OperationSignals,
|
|
272
|
+
): best is OperationTarget {
|
|
273
|
+
if (!best) return false;
|
|
274
|
+
return best.score >= 0.9
|
|
275
|
+
&& targetSignalMatches(best, signals)
|
|
276
|
+
&& operationMatches(best, signals.operationPath)
|
|
277
|
+
&& (!second || best.score - second.score >= 0.25);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function finalOperationResolution(
|
|
281
|
+
candidates: OperationTarget[],
|
|
282
|
+
signals: OperationSignals,
|
|
283
|
+
): OperationResolution {
|
|
162
284
|
const best = candidates[0];
|
|
163
285
|
const second = candidates[1];
|
|
164
286
|
if (signals.isDynamic && !signals.hasExplicitOverride && !signals.servicePath)
|
|
@@ -167,19 +289,13 @@ export function resolveOperation(
|
|
|
167
289
|
candidates,
|
|
168
290
|
reasons: ['dynamic_target_without_override'],
|
|
169
291
|
};
|
|
170
|
-
if (!
|
|
292
|
+
if (!strongSignal(signals))
|
|
171
293
|
return {
|
|
172
294
|
status: candidates.length > 1 ? 'ambiguous' : 'unresolved',
|
|
173
295
|
candidates,
|
|
174
296
|
reasons: ['operation_path_only_has_no_strong_target_signal'],
|
|
175
297
|
};
|
|
176
|
-
if (
|
|
177
|
-
best &&
|
|
178
|
-
best.score >= 0.9 &&
|
|
179
|
-
(best.servicePath === signals.servicePath || Boolean(signals.serviceName && (!best.reasons.includes('local_service_name_mismatch') || best.reasons.includes('same_repo_unique_operation_path_with_lookup_mismatch')))) &&
|
|
180
|
-
operationMatches(best, signals.operationPath) &&
|
|
181
|
-
(!second || best.score - second.score >= 0.25)
|
|
182
|
-
)
|
|
298
|
+
if (bestCandidateResolves(best, second, signals))
|
|
183
299
|
return {
|
|
184
300
|
status: 'resolved',
|
|
185
301
|
target: best,
|
|
@@ -192,6 +308,10 @@ export function resolveOperation(
|
|
|
192
308
|
reasons: ['candidate_score_below_resolution_threshold'],
|
|
193
309
|
};
|
|
194
310
|
}
|
|
311
|
+
|
|
312
|
+
function compareBinary(left: string, right: string): number {
|
|
313
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
314
|
+
}
|
|
195
315
|
function serviceMatches(candidate: OperationTarget, serviceName: string | undefined): boolean {
|
|
196
316
|
if (!serviceName) return false;
|
|
197
317
|
const simple = serviceName.split('.').at(-1) ?? serviceName;
|