@saptools/service-flow 0.1.65 → 0.1.67

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +67 -11
  3. package/TECHNICAL-NOTE.md +41 -1
  4. package/dist/{chunk-OONNRIDL.js → chunk-ZQABU7MR.js} +3764 -643
  5. package/dist/chunk-ZQABU7MR.js.map +1 -0
  6. package/dist/cli.js +158 -295
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +196 -1
  9. package/dist/index.js +7 -3
  10. package/dist/index.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/cli/002-doctor-lifecycle.ts +66 -0
  13. package/src/cli/doctor.ts +14 -27
  14. package/src/cli.ts +130 -102
  15. package/src/db/000-call-fact-repository.ts +537 -0
  16. package/src/db/001-fact-lifecycle.ts +111 -0
  17. package/src/db/migrations.ts +16 -1
  18. package/src/db/repositories.ts +1 -315
  19. package/src/db/schema.ts +4 -2
  20. package/src/index.ts +22 -0
  21. package/src/linker/004-event-subscription-handler-linker.ts +300 -0
  22. package/src/linker/cross-repo-linker.ts +23 -2
  23. package/src/output/001-compact-json-output.ts +6 -0
  24. package/src/parsers/imported-wrapper-parser.ts +4 -0
  25. package/src/parsers/outbound-call-parser.ts +11 -1
  26. package/src/parsers/symbol-parser.ts +7 -4
  27. package/src/trace/010-traversal-scope.ts +188 -0
  28. package/src/trace/011-event-subscriber-traversal.ts +366 -0
  29. package/src/trace/012-trace-graph-lookups.ts +74 -0
  30. package/src/trace/013-trace-root-scopes.ts +279 -0
  31. package/src/trace/014-compact-contract.ts +347 -0
  32. package/src/trace/015-trace-edge-recorder.ts +336 -0
  33. package/src/trace/016-compact-projector.ts +697 -0
  34. package/src/trace/017-trace-context.ts +378 -0
  35. package/src/trace/018-compact-trace.ts +87 -0
  36. package/src/trace/019-trace-edge-semantics.ts +328 -0
  37. package/src/trace/020-compact-field-projection.ts +387 -0
  38. package/src/trace/dynamic-branches.ts +35 -13
  39. package/src/trace/trace-engine.ts +271 -245
  40. package/src/types.ts +10 -0
  41. package/src/version.ts +1 -1
  42. package/dist/chunk-OONNRIDL.js.map +0 -1
@@ -0,0 +1,328 @@
1
+ import type { TraceEdge } from '../types.js';
2
+ import type { CompactSemanticEndpoint, CompactStatus } from './014-compact-contract.js';
3
+ import {
4
+ compactDecisionFromEvidence,
5
+ compactEventStatus,
6
+ compactGraphStatus,
7
+ compactRefs,
8
+ compactResolutionStatus,
9
+ compactSite,
10
+ semanticCallSource,
11
+ semanticGraphTarget,
12
+ semanticHandler,
13
+ semanticOperation,
14
+ semanticScopeTarget,
15
+ semanticSymbol,
16
+ type SemanticCallRow,
17
+ type SemanticTargetRow,
18
+ type TraceEdgeRecorder,
19
+ } from './015-trace-edge-recorder.js';
20
+ import type { PlannedEventSubscriberTransition } from './011-event-subscriber-traversal.js';
21
+ import type { DynamicCandidateBranch } from './dynamic-branches.js';
22
+
23
+ interface ImplementationObservation {
24
+ operationId: unknown;
25
+ handlerMethodId?: unknown;
26
+ handlerSymbolId?: unknown;
27
+ graphEdgeId?: unknown;
28
+ persistedStatus?: string;
29
+ persistedTargetKind?: string;
30
+ persistedTargetId?: string;
31
+ effectiveStatus: string;
32
+ strategy: string;
33
+ guided?: boolean;
34
+ contextual?: boolean;
35
+ unresolvedReason?: string;
36
+ evidence: Record<string, unknown>;
37
+ site: Record<string, unknown>;
38
+ }
39
+
40
+ interface LocalCallObservation {
41
+ symbolCall: Record<string, unknown>;
42
+ evidence: Record<string, unknown>;
43
+ unresolvedReason?: string;
44
+ }
45
+
46
+ interface ScopeObservation {
47
+ workspaceId?: number;
48
+ repositoryId?: number;
49
+ sourceFiles?: ReadonlySet<string>;
50
+ symbolIds?: ReadonlySet<number>;
51
+ structuralKey: string;
52
+ }
53
+
54
+ interface OutboundObservation {
55
+ call: SemanticCallRow;
56
+ row: SemanticTargetRow;
57
+ evidence: Record<string, unknown>;
58
+ workspaceId: number;
59
+ dynamicMode?: string;
60
+ unresolvedReason?: string;
61
+ }
62
+
63
+ export function recordImplementationObservation(
64
+ recorder: TraceEdgeRecorder,
65
+ edge: TraceEdge,
66
+ input: ImplementationObservation,
67
+ ): void {
68
+ const target = semanticHandler(input.handlerMethodId, input.handlerSymbolId,
69
+ () => recorder.unavailable('target', 'selected_handler'));
70
+ recorder.record(edge, {
71
+ source: semanticOperation(input.operationId,
72
+ () => recorder.unavailable('source', 'operation')),
73
+ target,
74
+ status: compactResolutionStatus(input.effectiveStatus, input.unresolvedReason),
75
+ decision: compactDecisionFromEvidence(input.evidence, {
76
+ effectiveResolutionStatus: input.unresolvedReason
77
+ ? 'unresolved' : input.effectiveStatus,
78
+ effectiveTarget: decisionTarget(target),
79
+ persistedResolutionStatus: input.persistedStatus,
80
+ persistedTarget: input.persistedTargetKind && input.persistedTargetId
81
+ ? { kind: input.persistedTargetKind, id: input.persistedTargetId }
82
+ : undefined,
83
+ implementationStrategy: input.strategy,
84
+ implementationGuided: input.guided,
85
+ implementationContextual: input.contextual,
86
+ reasonCode: input.unresolvedReason
87
+ ? 'selected_handler_unavailable' : undefined,
88
+ }),
89
+ refs: compactRefs({ graphEdgeId: input.graphEdgeId,
90
+ operationId: input.operationId,
91
+ handlerMethodId: input.handlerMethodId,
92
+ symbolId: input.handlerSymbolId }),
93
+ site: compactSite(input.site),
94
+ });
95
+ }
96
+
97
+ export function recordLocalCallObservation(
98
+ recorder: TraceEdgeRecorder,
99
+ edge: TraceEdge,
100
+ input: LocalCallObservation,
101
+ ): CompactSemanticEndpoint {
102
+ const source = semanticSymbol(input.symbolCall.caller_symbol_id,
103
+ () => recorder.unavailable('source', 'caller_symbol'));
104
+ const target = semanticSymbol(input.symbolCall.callee_symbol_id,
105
+ () => recorder.unavailable('target', 'callee_symbol'));
106
+ recorder.record(edge, {
107
+ source, target,
108
+ status: compactResolutionStatus(
109
+ input.symbolCall.status, input.unresolvedReason,
110
+ ),
111
+ decision: compactDecisionFromEvidence(input.evidence, {
112
+ effectiveResolutionStatus: String(input.symbolCall.status),
113
+ reasonCode: input.unresolvedReason ? 'symbol_call_unresolved' : undefined,
114
+ }),
115
+ refs: { symbolCallIds: idArray(input.symbolCall.id),
116
+ symbolIds: idArray(input.symbolCall.caller_symbol_id,
117
+ input.symbolCall.callee_symbol_id) },
118
+ site: compactSite(input.symbolCall),
119
+ });
120
+ return target;
121
+ }
122
+
123
+ export function recordCycleObservation(
124
+ recorder: TraceEdgeRecorder,
125
+ edge: TraceEdge,
126
+ source: CompactSemanticEndpoint,
127
+ scope: ScopeObservation,
128
+ refs: Record<string, unknown>,
129
+ site: Record<string, unknown>,
130
+ ): void {
131
+ recorder.record(edge, {
132
+ source,
133
+ target: scope.workspaceId === undefined
134
+ ? recorder.unavailable('target', 'cycle_scope')
135
+ : semanticScopeTarget(scope.workspaceId, scope.repositoryId,
136
+ scope.sourceFiles, scope.symbolIds, scope.structuralKey),
137
+ status: 'cycle',
138
+ decision: compactDecisionFromEvidence(edge.evidence, {
139
+ reasonCode: 'structural_ancestry_cycle',
140
+ }),
141
+ refs: compactRefs(refs),
142
+ site: compactSite(site),
143
+ });
144
+ }
145
+
146
+ export function recordOutboundObservation(
147
+ recorder: TraceEdgeRecorder,
148
+ edge: TraceEdge,
149
+ input: OutboundObservation,
150
+ ): { source: CompactSemanticEndpoint; target: CompactSemanticEndpoint } {
151
+ const source = semanticCallSource(input.call, input.workspaceId);
152
+ const target = semanticGraphTarget(
153
+ input.row, input.call, input.workspaceId,
154
+ () => recorder.unavailable('target', input.row.to_kind),
155
+ );
156
+ recorder.record(edge, {
157
+ source, target,
158
+ status: compactGraphStatus(input.row, input.evidence,
159
+ input.unresolvedReason, input.dynamicMode),
160
+ decision: compactDecisionFromEvidence(input.evidence, {
161
+ reasonCode: input.unresolvedReason
162
+ ? safeReasonCode(input.evidence.reasonCode, 'outbound_target_unresolved')
163
+ : undefined,
164
+ }),
165
+ refs: compactRefs({
166
+ graphEdgeId: positiveId(input.evidence.persistedGraphEdgeId),
167
+ outboundCallId: input.call.id,
168
+ operationId: input.row.to_kind === 'operation' ? input.row.to_id : undefined,
169
+ symbolId: input.call.source_symbol_id,
170
+ }),
171
+ site: compactSite(input.call),
172
+ });
173
+ return { source, target };
174
+ }
175
+
176
+ export function recordEventBridgeObservation(
177
+ recorder: TraceEdgeRecorder,
178
+ edge: TraceEdge,
179
+ plan: PlannedEventSubscriberTransition,
180
+ workspaceId: number,
181
+ subscriptionCount: number,
182
+ ): CompactSemanticEndpoint {
183
+ const handler = plan.transition.handler;
184
+ const target: CompactSemanticEndpoint = handler
185
+ ? { kind: 'symbol', symbolId: handler.symbolId }
186
+ : { kind: 'target', workspaceId,
187
+ repositoryId: plan.transition.subscriptionRepoId,
188
+ targetKind: plan.transition.targetKind,
189
+ targetId: plan.transition.targetId };
190
+ recorder.record(edge, {
191
+ source: { kind: 'event', workspaceId,
192
+ eventName: plan.transition.eventName },
193
+ target,
194
+ status: compactEventStatus(plan.transition.status, plan.bodyExpansion),
195
+ decision: compactDecisionFromEvidence(plan.evidence, {
196
+ eventSubscriptionCount: subscriptionCount,
197
+ reasonCode: plan.transition.reasonCode,
198
+ }),
199
+ refs: eventReferences(plan),
200
+ site: eventSite(plan),
201
+ });
202
+ return target;
203
+ }
204
+
205
+ export function recordEventCycleObservation(
206
+ recorder: TraceEdgeRecorder,
207
+ edge: TraceEdge,
208
+ plan: PlannedEventSubscriberTransition,
209
+ source: CompactSemanticEndpoint,
210
+ workspaceId: number,
211
+ ): void {
212
+ const handler = plan.transition.handler;
213
+ if (!plan.state) return;
214
+ recorder.record(edge, {
215
+ source,
216
+ target: semanticScopeTarget(workspaceId, handler?.repoId,
217
+ handler ? new Set([handler.sourceFile]) : undefined,
218
+ handler ? new Set([handler.symbolId]) : undefined,
219
+ plan.state.structuralKey),
220
+ status: 'cycle',
221
+ decision: compactDecisionFromEvidence(edge.evidence, {
222
+ reasonCode: 'structural_ancestry_cycle',
223
+ dispatchCertainty: 'static_name_only',
224
+ }),
225
+ refs: eventReferences(plan),
226
+ site: eventSite(plan),
227
+ });
228
+ }
229
+
230
+ export function recordDynamicBranchObservation(
231
+ recorder: TraceEdgeRecorder,
232
+ branch: DynamicCandidateBranch,
233
+ call: SemanticCallRow,
234
+ source: CompactSemanticEndpoint,
235
+ evidence: Record<string, unknown>,
236
+ workspaceId: number,
237
+ ): void {
238
+ const target = dynamicBranchTarget(recorder, branch, workspaceId);
239
+ recorder.record(branch.edge, {
240
+ source, target, status: 'dynamic',
241
+ decision: compactDecisionFromEvidence(evidence, {
242
+ dynamicMode: 'candidates',
243
+ effectiveTarget: branch.operationId === undefined
244
+ ? undefined : { kind: 'operation', id: String(branch.operationId) },
245
+ remediationCode: 'provide_runtime_variables',
246
+ }),
247
+ refs: compactRefs({ graphEdgeId: positiveId(evidence.persistedGraphEdgeId),
248
+ outboundCallId: call.id, operationId: branch.operationId,
249
+ symbolId: call.source_symbol_id }),
250
+ site: compactSite(call),
251
+ });
252
+ }
253
+
254
+ function dynamicBranchTarget(
255
+ recorder: TraceEdgeRecorder,
256
+ branch: DynamicCandidateBranch,
257
+ workspaceId: number,
258
+ ): CompactSemanticEndpoint {
259
+ if (branch.operationId !== undefined)
260
+ return { kind: 'operation', operationId: branch.operationId };
261
+ if (branch.servicePath && branch.operationPath) return {
262
+ kind: 'target', workspaceId,
263
+ repositoryId: branch.repositoryId,
264
+ targetKind: 'dynamic_operation_candidate',
265
+ targetId: JSON.stringify([branch.servicePath, branch.operationPath]),
266
+ };
267
+ return recorder.unavailable('target', 'dynamic_operation_candidate');
268
+ }
269
+
270
+ export function graphObservationStatus(
271
+ row: SemanticTargetRow,
272
+ evidence: Record<string, unknown>,
273
+ unresolvedReason: string | undefined,
274
+ dynamicMode: string | undefined,
275
+ ): CompactStatus {
276
+ return compactGraphStatus(row, evidence, unresolvedReason, dynamicMode);
277
+ }
278
+
279
+ function eventReferences(
280
+ plan: PlannedEventSubscriberTransition,
281
+ ): ReturnType<typeof compactRefs> {
282
+ return compactRefs({ graphEdgeId: plan.transition.graphEdgeId,
283
+ subscribeCallId: plan.transition.subscribeCallId,
284
+ symbolCallId: plan.transition.symbolCallId,
285
+ symbolId: plan.transition.handler?.symbolId });
286
+ }
287
+
288
+ function eventSite(
289
+ plan: PlannedEventSubscriberTransition,
290
+ ): ReturnType<typeof compactSite> {
291
+ return compactSite({ repository: plan.transition.subscriptionRepoName,
292
+ sourceFile: plan.transition.sourceFile,
293
+ sourceLine: plan.transition.sourceLine,
294
+ startOffset: plan.transition.callSiteStartOffset,
295
+ endOffset: plan.transition.callSiteEndOffset });
296
+ }
297
+
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
+ function positiveId(value: unknown): number | string | undefined {
304
+ if (typeof value === 'number') return Number.isFinite(value) && value > 0
305
+ ? value : undefined;
306
+ if (typeof value !== 'string' || value.length === 0) return undefined;
307
+ const numeric = Number(value);
308
+ if (Number.isSafeInteger(numeric)) return numeric > 0 ? numeric : undefined;
309
+ return value;
310
+ }
311
+
312
+ function idArray(...values: unknown[]): Array<number | string> | undefined {
313
+ const ids = values.flatMap((value) => {
314
+ const id = positiveId(value);
315
+ return id === undefined ? [] : [id];
316
+ });
317
+ return ids.length > 0 ? ids : undefined;
318
+ }
319
+
320
+ function decisionTarget(
321
+ endpoint: CompactSemanticEndpoint,
322
+ ): { kind: string; id: string } | undefined {
323
+ if (endpoint.kind === 'symbol')
324
+ return { kind: 'symbol', id: String(endpoint.symbolId) };
325
+ if (endpoint.kind === 'handler_method')
326
+ return { kind: 'handler_method', id: String(endpoint.handlerMethodId) };
327
+ return undefined;
328
+ }