@saptools/service-flow 0.1.65 → 0.1.66
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 +12 -0
- package/README.md +67 -11
- package/TECHNICAL-NOTE.md +41 -1
- package/dist/{chunk-OONNRIDL.js → chunk-TOVX4WYH.js} +3764 -643
- package/dist/chunk-TOVX4WYH.js.map +1 -0
- package/dist/cli.js +158 -295
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +196 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/002-doctor-lifecycle.ts +66 -0
- package/src/cli/doctor.ts +14 -27
- package/src/cli.ts +130 -102
- package/src/db/000-call-fact-repository.ts +537 -0
- package/src/db/001-fact-lifecycle.ts +111 -0
- package/src/db/migrations.ts +16 -1
- package/src/db/repositories.ts +1 -315
- package/src/db/schema.ts +4 -2
- package/src/index.ts +22 -0
- package/src/linker/004-event-subscription-handler-linker.ts +300 -0
- package/src/linker/cross-repo-linker.ts +23 -2
- package/src/output/001-compact-json-output.ts +6 -0
- package/src/parsers/imported-wrapper-parser.ts +4 -0
- package/src/parsers/outbound-call-parser.ts +11 -1
- package/src/parsers/symbol-parser.ts +7 -4
- package/src/trace/010-traversal-scope.ts +188 -0
- package/src/trace/011-event-subscriber-traversal.ts +366 -0
- package/src/trace/012-trace-graph-lookups.ts +74 -0
- package/src/trace/013-trace-root-scopes.ts +279 -0
- package/src/trace/014-compact-contract.ts +347 -0
- package/src/trace/015-trace-edge-recorder.ts +336 -0
- package/src/trace/016-compact-projector.ts +697 -0
- package/src/trace/017-trace-context.ts +378 -0
- package/src/trace/018-compact-trace.ts +87 -0
- package/src/trace/019-trace-edge-semantics.ts +328 -0
- package/src/trace/020-compact-field-projection.ts +387 -0
- package/src/trace/dynamic-branches.ts +35 -13
- package/src/trace/trace-engine.ts +271 -245
- package/src/types.ts +10 -0
- package/src/version.ts +1 -1
- package/dist/chunk-OONNRIDL.js.map +0 -1
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import type { TraceEdge } from '../types.js';
|
|
2
|
+
import type {
|
|
3
|
+
CompactDecisionInput,
|
|
4
|
+
CompactEdgeObservation,
|
|
5
|
+
CompactReferenceInput,
|
|
6
|
+
CompactSemanticEndpoint,
|
|
7
|
+
CompactSourceSite,
|
|
8
|
+
CompactStatus,
|
|
9
|
+
CompactTraceObserver,
|
|
10
|
+
} from './014-compact-contract.js';
|
|
11
|
+
|
|
12
|
+
export interface TraceEdgeSemantics {
|
|
13
|
+
source: CompactSemanticEndpoint;
|
|
14
|
+
target: CompactSemanticEndpoint;
|
|
15
|
+
status: CompactStatus;
|
|
16
|
+
decision?: CompactDecisionInput;
|
|
17
|
+
refs?: CompactReferenceInput;
|
|
18
|
+
site?: CompactSourceSite;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SemanticCallRow extends Record<string, unknown> {
|
|
22
|
+
id: number;
|
|
23
|
+
repo_id: number;
|
|
24
|
+
repoName: string;
|
|
25
|
+
source_file: string;
|
|
26
|
+
source_line: number;
|
|
27
|
+
source_symbol_id?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SemanticTargetRow extends Record<string, unknown> {
|
|
31
|
+
to_kind: string;
|
|
32
|
+
to_id: string;
|
|
33
|
+
status?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const nonResolvedGraphStatuses: Readonly<Record<string, CompactStatus>> = {
|
|
37
|
+
ambiguous: 'ambiguous', dynamic: 'dynamic', terminal: 'terminal',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export class TraceEdgeRecorder {
|
|
41
|
+
constructor(
|
|
42
|
+
private readonly edges: TraceEdge[],
|
|
43
|
+
private readonly observer?: CompactTraceObserver,
|
|
44
|
+
) {}
|
|
45
|
+
|
|
46
|
+
record(edge: TraceEdge, semantics: TraceEdgeSemantics): number {
|
|
47
|
+
const ordinal = this.edges.length;
|
|
48
|
+
this.edges.push(edge);
|
|
49
|
+
this.observer?.record(observation(ordinal, edge, semantics));
|
|
50
|
+
return ordinal;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
unavailable(
|
|
54
|
+
side: 'source' | 'target',
|
|
55
|
+
endpointKind: string,
|
|
56
|
+
): CompactSemanticEndpoint {
|
|
57
|
+
return { kind: 'unavailable', side, endpointKind,
|
|
58
|
+
detailedEdgeIndex: this.edges.length };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function semanticCallSource(
|
|
63
|
+
call: SemanticCallRow,
|
|
64
|
+
workspaceId: number,
|
|
65
|
+
): CompactSemanticEndpoint {
|
|
66
|
+
const symbolId = positiveNumber(call.source_symbol_id);
|
|
67
|
+
if (symbolId !== undefined) return { kind: 'symbol', symbolId };
|
|
68
|
+
return {
|
|
69
|
+
kind: 'call_site', workspaceId, repositoryId: call.repo_id,
|
|
70
|
+
repositoryName: call.repoName, sourceFile: call.source_file,
|
|
71
|
+
sourceLine: call.source_line,
|
|
72
|
+
startOffset: finiteNumber(call.call_site_start_offset),
|
|
73
|
+
endOffset: finiteNumber(call.call_site_end_offset), callId: call.id,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function semanticOperation(
|
|
78
|
+
value: unknown,
|
|
79
|
+
unavailable: () => CompactSemanticEndpoint,
|
|
80
|
+
): CompactSemanticEndpoint {
|
|
81
|
+
const operationId = positiveNumber(value);
|
|
82
|
+
return operationId === undefined ? unavailable()
|
|
83
|
+
: { kind: 'operation', operationId };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function semanticSymbol(
|
|
87
|
+
value: unknown,
|
|
88
|
+
unavailable: () => CompactSemanticEndpoint,
|
|
89
|
+
): CompactSemanticEndpoint {
|
|
90
|
+
const symbolId = positiveNumber(value);
|
|
91
|
+
return symbolId === undefined ? unavailable() : { kind: 'symbol', symbolId };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function semanticHandler(
|
|
95
|
+
methodIdValue: unknown,
|
|
96
|
+
symbolIdValue: unknown,
|
|
97
|
+
unavailable: () => CompactSemanticEndpoint,
|
|
98
|
+
): CompactSemanticEndpoint {
|
|
99
|
+
const symbolId = positiveNumber(symbolIdValue);
|
|
100
|
+
if (symbolId !== undefined) return { kind: 'symbol', symbolId };
|
|
101
|
+
const handlerMethodId = positiveNumber(methodIdValue);
|
|
102
|
+
return handlerMethodId === undefined ? unavailable()
|
|
103
|
+
: { kind: 'handler_method', handlerMethodId };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function semanticGraphTarget(
|
|
107
|
+
row: SemanticTargetRow,
|
|
108
|
+
call: Record<string, unknown>,
|
|
109
|
+
workspaceId: number,
|
|
110
|
+
unavailable: () => CompactSemanticEndpoint,
|
|
111
|
+
): CompactSemanticEndpoint {
|
|
112
|
+
if (row.to_kind === 'event') return {
|
|
113
|
+
kind: 'event', workspaceId,
|
|
114
|
+
eventName: typeof call.event_name_expr === 'string'
|
|
115
|
+
? call.event_name_expr : row.to_id,
|
|
116
|
+
};
|
|
117
|
+
const id = positiveNumber(row.to_id);
|
|
118
|
+
if (row.to_kind === 'operation')
|
|
119
|
+
return id === undefined ? unavailable() : { kind: 'operation', operationId: id };
|
|
120
|
+
if (row.to_kind === 'symbol')
|
|
121
|
+
return id === undefined ? unavailable() : { kind: 'symbol', symbolId: id };
|
|
122
|
+
if (row.to_kind === 'handler_method') return id === undefined
|
|
123
|
+
? unavailable() : { kind: 'handler_method', handlerMethodId: id };
|
|
124
|
+
return { kind: 'target', workspaceId,
|
|
125
|
+
repositoryId: positiveNumber(call.repo_id), targetKind: row.to_kind,
|
|
126
|
+
targetId: row.to_id };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function semanticScopeTarget(
|
|
130
|
+
workspaceId: number,
|
|
131
|
+
repositoryId: number | undefined,
|
|
132
|
+
sourceFiles: ReadonlySet<string> | undefined,
|
|
133
|
+
symbolIds: ReadonlySet<number> | undefined,
|
|
134
|
+
structuralKey: string,
|
|
135
|
+
): CompactSemanticEndpoint {
|
|
136
|
+
return {
|
|
137
|
+
kind: 'scope', workspaceId, repositoryId,
|
|
138
|
+
sourceFiles: [...(sourceFiles ?? [])],
|
|
139
|
+
symbolIds: [...(symbolIds ?? [])], structuralKey,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function compactGraphStatus(
|
|
144
|
+
row: SemanticTargetRow,
|
|
145
|
+
evidence: Record<string, unknown>,
|
|
146
|
+
unresolvedReason: string | undefined,
|
|
147
|
+
dynamicMode: string | undefined,
|
|
148
|
+
): CompactStatus {
|
|
149
|
+
const effective = recordValue(evidence.effectiveResolution);
|
|
150
|
+
const status = stringValue(effective.status) ?? row.status ?? 'unresolved';
|
|
151
|
+
if (status !== 'resolved')
|
|
152
|
+
return nonResolvedGraphStatuses[status] ?? 'unresolved';
|
|
153
|
+
if (unresolvedReason) return 'unresolved';
|
|
154
|
+
const inference = recordValue(evidence.dynamicTargetInference);
|
|
155
|
+
if (isDynamicInference(dynamicMode, inference)) return 'dynamic';
|
|
156
|
+
return traversableTargetKind(row.to_kind) ? 'resolved' : 'terminal';
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function isDynamicInference(
|
|
160
|
+
mode: string | undefined,
|
|
161
|
+
evidence: Record<string, unknown>,
|
|
162
|
+
): boolean {
|
|
163
|
+
return mode === 'infer' && evidence.status === 'resolved';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function traversableTargetKind(kind: string): boolean {
|
|
167
|
+
return ['operation', 'symbol', 'handler_method'].includes(kind);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function compactResolutionStatus(
|
|
171
|
+
status: unknown,
|
|
172
|
+
unresolvedReason?: string,
|
|
173
|
+
): CompactStatus {
|
|
174
|
+
if (status === 'ambiguous') return 'ambiguous';
|
|
175
|
+
if (status === 'dynamic') return 'dynamic';
|
|
176
|
+
return status === 'resolved' && !unresolvedReason ? 'resolved' : 'unresolved';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function compactEventStatus(
|
|
180
|
+
status: unknown,
|
|
181
|
+
bodyExpansion: unknown,
|
|
182
|
+
): CompactStatus {
|
|
183
|
+
if (bodyExpansion === 'cycle_blocked') return 'cycle';
|
|
184
|
+
if (status === 'ambiguous') return 'ambiguous';
|
|
185
|
+
return status === 'resolved' ? 'inferred' : 'unresolved';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function compactDecisionFromEvidence(
|
|
189
|
+
evidence: Record<string, unknown>,
|
|
190
|
+
overrides: CompactDecisionInput = {},
|
|
191
|
+
): CompactDecisionInput {
|
|
192
|
+
const effective = recordValue(evidence.effectiveResolution);
|
|
193
|
+
const persisted = recordValue(evidence.persistedResolution);
|
|
194
|
+
const dynamic = recordValue(evidence.dynamicTargetExploration);
|
|
195
|
+
const implementation = recordValue(evidence.implementationSelection);
|
|
196
|
+
const missing = stringArray(dynamic.missingVariables
|
|
197
|
+
?? evidence.missingRuntimeVariables);
|
|
198
|
+
const authoritativeMissingCount = finiteNumber(
|
|
199
|
+
dynamic.missingVariableCount ?? evidence.missingVariableCount,
|
|
200
|
+
);
|
|
201
|
+
return {
|
|
202
|
+
effectiveResolutionStatus: stringValue(effective.status),
|
|
203
|
+
effectiveTarget: targetSummary(effective),
|
|
204
|
+
persistedResolutionStatus: stringValue(persisted.status),
|
|
205
|
+
persistedTarget: targetSummary(persisted),
|
|
206
|
+
missingVariableNames: missing.length > 0 ? missing : undefined,
|
|
207
|
+
missingVariableCount: authoritativeMissingCount
|
|
208
|
+
?? (missing.length > 0 ? missing.length : undefined),
|
|
209
|
+
dynamicMode: dynamicMode(dynamic.mode),
|
|
210
|
+
candidateCount: firstNumber(
|
|
211
|
+
dynamic.candidateCount,
|
|
212
|
+
implementation.candidateCount,
|
|
213
|
+
implementation.candidateScoreCount,
|
|
214
|
+
evidence.candidateCount,
|
|
215
|
+
evidence.persistedCandidateCount,
|
|
216
|
+
),
|
|
217
|
+
viableCandidateCount: finiteNumber(dynamic.viableCandidateCount),
|
|
218
|
+
rejectedCandidateCount: finiteNumber(dynamic.rejectedCandidateCount),
|
|
219
|
+
omittedCandidateCount: finiteNumber(dynamic.omittedCandidateCount),
|
|
220
|
+
implementationStrategy: stringValue(implementation.strategy),
|
|
221
|
+
implementationGuided: booleanValue(implementation.guided),
|
|
222
|
+
implementationContextual: booleanValue(
|
|
223
|
+
evidence.contextualImplementationSelected),
|
|
224
|
+
reasonCode: stringValue(evidence.reasonCode ?? evidence.cycleReason),
|
|
225
|
+
eventMatchStrategy: stringValue(evidence.matchStrategy),
|
|
226
|
+
dispatchCertainty: stringValue(evidence.dispatchCertainty),
|
|
227
|
+
associationStatus: stringValue(evidence.associationStatus),
|
|
228
|
+
associationBasis: stringValue(evidence.associationBasis),
|
|
229
|
+
eventScope: stringValue(evidence.dispatchScope),
|
|
230
|
+
callRole: stringValue(evidence.callRole),
|
|
231
|
+
factOrigin: stringValue(evidence.factOrigin),
|
|
232
|
+
roleSiteMatchCount: finiteNumber(evidence.roleSiteMatchCount),
|
|
233
|
+
bodyExpansion: stringValue(evidence.bodyExpansion),
|
|
234
|
+
...overrides,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function compactRefs(
|
|
239
|
+
values: Record<string, unknown>,
|
|
240
|
+
): CompactReferenceInput {
|
|
241
|
+
return {
|
|
242
|
+
graphEdgeIds: reference(values.graphEdgeId),
|
|
243
|
+
outboundCallIds: reference(values.outboundCallId),
|
|
244
|
+
subscribeCallIds: reference(values.subscribeCallId),
|
|
245
|
+
symbolCallIds: reference(values.symbolCallId),
|
|
246
|
+
operationIds: reference(values.operationId),
|
|
247
|
+
symbolIds: reference(values.symbolId),
|
|
248
|
+
handlerMethodIds: reference(values.handlerMethodId),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function compactSite(
|
|
253
|
+
values: Record<string, unknown>,
|
|
254
|
+
): CompactSourceSite {
|
|
255
|
+
return {
|
|
256
|
+
repository: stringValue(values.repository ?? values.repoName),
|
|
257
|
+
sourceFile: stringValue(values.sourceFile ?? values.source_file),
|
|
258
|
+
sourceLine: finiteNumber(values.sourceLine ?? values.source_line),
|
|
259
|
+
startOffset: finiteNumber(values.startOffset ?? values.call_site_start_offset),
|
|
260
|
+
endOffset: finiteNumber(values.endOffset ?? values.call_site_end_offset),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function observation(
|
|
265
|
+
ordinal: number,
|
|
266
|
+
edge: TraceEdge,
|
|
267
|
+
semantics: TraceEdgeSemantics,
|
|
268
|
+
): CompactEdgeObservation {
|
|
269
|
+
return {
|
|
270
|
+
ordinal, step: edge.step, type: edge.type,
|
|
271
|
+
source: semantics.source, target: semantics.target,
|
|
272
|
+
status: semantics.status, confidence: edge.confidence,
|
|
273
|
+
decision: semantics.decision, refs: semantics.refs, site: semantics.site,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function targetSummary(
|
|
278
|
+
value: Record<string, unknown>,
|
|
279
|
+
): { kind: string; id: string } | undefined {
|
|
280
|
+
const kind = stringValue(value.targetKind);
|
|
281
|
+
const id = stringValue(value.targetId);
|
|
282
|
+
return kind && id ? { kind, id } : undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function reference(value: unknown): Array<number | string> | undefined {
|
|
286
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
287
|
+
const numeric = Number(value);
|
|
288
|
+
if (Number.isSafeInteger(numeric)) return numeric > 0 ? [numeric] : undefined;
|
|
289
|
+
return [value];
|
|
290
|
+
}
|
|
291
|
+
const number = finiteNumber(value);
|
|
292
|
+
return number === undefined || number <= 0 ? undefined : [number];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function dynamicMode(value: unknown): 'strict' | 'candidates' | 'infer' | undefined {
|
|
296
|
+
return value === 'strict' || value === 'candidates' || value === 'infer'
|
|
297
|
+
? value : undefined;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function recordValue(value: unknown): Record<string, unknown> {
|
|
301
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
302
|
+
? value as Record<string, unknown> : {};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function stringArray(value: unknown): string[] {
|
|
306
|
+
return Array.isArray(value)
|
|
307
|
+
? value.filter((item): item is string => typeof item === 'string') : [];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function stringValue(value: unknown): string | undefined {
|
|
311
|
+
return typeof value === 'string' ? value : undefined;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function finiteNumber(value: unknown): number | undefined {
|
|
315
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
316
|
+
if (typeof value !== 'string' || value.trim() === '') return undefined;
|
|
317
|
+
const parsed = Number(value);
|
|
318
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function positiveNumber(value: unknown): number | undefined {
|
|
322
|
+
const number = finiteNumber(value);
|
|
323
|
+
return number !== undefined && number > 0 ? number : undefined;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function booleanValue(value: unknown): boolean | undefined {
|
|
327
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function firstNumber(...values: unknown[]): number | undefined {
|
|
331
|
+
for (const value of values) {
|
|
332
|
+
const numeric = finiteNumber(value);
|
|
333
|
+
if (numeric !== undefined) return numeric;
|
|
334
|
+
}
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|