@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +67 -11
  3. package/TECHNICAL-NOTE.md +41 -1
  4. package/dist/{chunk-OONNRIDL.js → chunk-TOVX4WYH.js} +3764 -643
  5. package/dist/chunk-TOVX4WYH.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,279 @@
1
+ import type { Db } from '../db/connection.js';
2
+ import { currentFactLifecycleDiagnostic } from '../db/001-fact-lifecycle.js';
3
+ import type { ContextBinding } from './008-contextual-runtime-state.js';
4
+ import {
5
+ resolveTraversalWorkspaceId,
6
+ type TraversalScopeScheduler,
7
+ type TraversalScopeState,
8
+ } from './010-traversal-scope.js';
9
+
10
+ export interface TraceQueueScope {
11
+ repoId?: number;
12
+ files?: Set<string>;
13
+ symbolIds?: Set<number>;
14
+ depth: number;
15
+ context: Map<string, ContextBinding>;
16
+ state: TraversalScopeState;
17
+ unownedOnly?: boolean;
18
+ rootObservationOnly?: boolean;
19
+ }
20
+
21
+ export interface PendingTraceRootScope {
22
+ repoId: number;
23
+ files: Set<string>;
24
+ symbolIds: Set<number>;
25
+ unownedOnly: boolean;
26
+ rootObservationOnly: boolean;
27
+ }
28
+
29
+ interface RootCallRow {
30
+ id: number;
31
+ repoId: number;
32
+ repoName: string;
33
+ workspaceId: number;
34
+ graphGeneration: number;
35
+ sourceSymbolId?: number | null;
36
+ sourceFile: string;
37
+ callType: string;
38
+ eventName?: string | null;
39
+ }
40
+
41
+ export interface TraceRootPlan {
42
+ workspaceId?: number;
43
+ queue: TraceQueueScope[];
44
+ pendingRoots: PendingTraceRootScope[];
45
+ diagnostic?: Record<string, unknown>;
46
+ }
47
+
48
+ export function createTraceRootPlan(
49
+ db: Db,
50
+ scheduler: TraversalScopeScheduler,
51
+ scope: {
52
+ repoId?: number;
53
+ files?: Set<string>;
54
+ symbolIds?: Set<number>;
55
+ selectorMatched: boolean;
56
+ },
57
+ requestedWorkspaceId: number | undefined,
58
+ includeAsync: boolean,
59
+ ): TraceRootPlan {
60
+ const workspaceId = resolveTraversalWorkspaceId(
61
+ db, requestedWorkspaceId, scope.repoId,
62
+ );
63
+ if (workspaceId !== undefined) {
64
+ const lifecycle = currentFactLifecycleDiagnostic(db, workspaceId);
65
+ if (lifecycle) return {
66
+ workspaceId, queue: [], pendingRoots: [], diagnostic: lifecycle,
67
+ };
68
+ }
69
+ if (!scope.selectorMatched)
70
+ return { workspaceId, queue: [], pendingRoots: [] };
71
+ if (workspaceId === undefined) return {
72
+ workspaceId, queue: [], pendingRoots: [],
73
+ diagnostic: workspaceAmbiguityDiagnostic(db),
74
+ };
75
+ const pendingRoots = includeAsync
76
+ ? rootScopes(db, workspaceId, scope) : undefined;
77
+ if (pendingRoots)
78
+ return { workspaceId, queue: [], pendingRoots };
79
+ return {
80
+ workspaceId,
81
+ queue: initialQueue(scheduler, workspaceId, scope),
82
+ pendingRoots: [],
83
+ };
84
+ }
85
+
86
+ export function nextPendingRoot(
87
+ pendingRoots: PendingTraceRootScope[],
88
+ scheduler: TraversalScopeScheduler,
89
+ workspaceId: number,
90
+ ): TraceQueueScope | undefined {
91
+ while (pendingRoots.length > 0) {
92
+ const root = pendingRoots.shift();
93
+ if (!root) return undefined;
94
+ const context = new Map<string, ContextBinding>();
95
+ const scheduled = scheduler.schedule({
96
+ workspaceId, repoId: root.repoId, files: root.files,
97
+ symbolIds: root.symbolIds, context,
98
+ });
99
+ if (scheduled.kind !== 'scheduled') continue;
100
+ return { ...root, depth: 1, context, state: scheduled.state };
101
+ }
102
+ return undefined;
103
+ }
104
+
105
+ export function claimPendingRoot(
106
+ pendingRoots: PendingTraceRootScope[],
107
+ target: {
108
+ repoId?: number;
109
+ files: ReadonlySet<string>;
110
+ symbolIds: ReadonlySet<number>;
111
+ },
112
+ ): boolean {
113
+ const index = pendingRoots.findIndex((root) =>
114
+ target.repoId === root.repoId
115
+ && !root.unownedOnly
116
+ && setsEqual(root.files, target.files)
117
+ && setsEqual(root.symbolIds, target.symbolIds));
118
+ if (index < 0) return false;
119
+ pendingRoots.splice(index, 1);
120
+ return true;
121
+ }
122
+
123
+ export function enqueueCausalScope(
124
+ queue: TraceQueueScope[],
125
+ pendingRoots: PendingTraceRootScope[],
126
+ scope: TraceQueueScope,
127
+ ): void {
128
+ if (scope.context.size === 0 && scope.files && scope.symbolIds)
129
+ claimPendingRoot(pendingRoots, {
130
+ repoId: scope.repoId, files: scope.files, symbolIds: scope.symbolIds,
131
+ });
132
+ queue.push(scope);
133
+ }
134
+
135
+ function initialQueue(
136
+ scheduler: TraversalScopeScheduler,
137
+ workspaceId: number,
138
+ scope: { repoId?: number; files?: Set<string>; symbolIds?: Set<number> },
139
+ ): TraceQueueScope[] {
140
+ const context = new Map<string, ContextBinding>();
141
+ const scheduled = scheduler.schedule({
142
+ workspaceId, repoId: scope.repoId, files: scope.files,
143
+ symbolIds: scope.symbolIds, context,
144
+ });
145
+ return scheduled.kind === 'scheduled' ? [{
146
+ repoId: scope.repoId, files: scope.files, symbolIds: scope.symbolIds,
147
+ depth: 1, context, state: scheduled.state,
148
+ }] : [];
149
+ }
150
+
151
+ function rootScopes(
152
+ db: Db,
153
+ workspaceId: number,
154
+ scope: { repoId?: number; files?: Set<string>; symbolIds?: Set<number> },
155
+ ): PendingTraceRootScope[] | undefined {
156
+ if (scope.symbolIds && scope.symbolIds.size === 1) return undefined;
157
+ const calls = scopedRootCalls(db, workspaceId, scope);
158
+ if (!hasExactDispatch(db, calls)) return undefined;
159
+ if (scope.symbolIds && scope.symbolIds.size > 1)
160
+ return selectedSymbolRoots(db, workspaceId, scope.symbolIds);
161
+ return callOwnerRoots(calls);
162
+ }
163
+
164
+ function scopedRootCalls(
165
+ db: Db,
166
+ workspaceId: number,
167
+ scope: { repoId?: number; files?: Set<string>; symbolIds?: Set<number> },
168
+ ): RootCallRow[] {
169
+ return rootCalls(db, workspaceId, scope.repoId, scope.files)
170
+ .filter((call) => !scope.symbolIds
171
+ || (typeof call.sourceSymbolId === 'number'
172
+ && scope.symbolIds.has(call.sourceSymbolId)));
173
+ }
174
+
175
+ function callOwnerRoots(
176
+ calls: RootCallRow[],
177
+ ): PendingTraceRootScope[] | undefined {
178
+ const roots = new Map<string, PendingTraceRootScope>();
179
+ for (const call of calls) {
180
+ const [key, root] = callOwnerRoot(call);
181
+ if (roots.has(key)) continue;
182
+ roots.set(key, root);
183
+ }
184
+ return roots.size > 0 ? [...roots.values()] : undefined;
185
+ }
186
+
187
+ function callOwnerRoot(
188
+ call: RootCallRow,
189
+ ): [string, PendingTraceRootScope] {
190
+ const symbolId = call.sourceSymbolId;
191
+ const owned = typeof symbolId === 'number';
192
+ const key = owned
193
+ ? `symbol:${symbolId}` : `unowned:${call.repoId}:${call.sourceFile}`;
194
+ return [key, {
195
+ repoId: call.repoId,
196
+ files: new Set([call.sourceFile]),
197
+ symbolIds: owned ? new Set([symbolId]) : new Set(),
198
+ unownedOnly: !owned,
199
+ rootObservationOnly: true,
200
+ }];
201
+ }
202
+
203
+ function selectedSymbolRoots(
204
+ db: Db,
205
+ workspaceId: number,
206
+ symbolIds: Set<number>,
207
+ ): PendingTraceRootScope[] {
208
+ const ids = [...symbolIds];
209
+ const rows = db.prepare(`SELECT s.id,s.repo_id repoId,s.source_file sourceFile
210
+ FROM symbols s JOIN repositories r ON r.id=s.repo_id
211
+ WHERE r.workspace_id=? AND s.id IN (${ids.map(() => '?').join(',')})
212
+ ORDER BY r.name COLLATE BINARY,r.id,s.source_file COLLATE BINARY,
213
+ s.start_offset,s.end_offset,s.id`).all(workspaceId, ...ids);
214
+ return rows.flatMap((row) => typeof row.id === 'number'
215
+ && typeof row.repoId === 'number' && typeof row.sourceFile === 'string'
216
+ ? [{ repoId: row.repoId, files: new Set([row.sourceFile]),
217
+ symbolIds: new Set([row.id]), unownedOnly: false,
218
+ rootObservationOnly: false }]
219
+ : []);
220
+ }
221
+
222
+ function setsEqual<T>(left: ReadonlySet<T>, right: ReadonlySet<T>): boolean {
223
+ if (left.size !== right.size) return false;
224
+ return [...left].every((value) => right.has(value));
225
+ }
226
+
227
+ function rootCalls(
228
+ db: Db,
229
+ workspaceId: number,
230
+ repoId: number | undefined,
231
+ files: Set<string> | undefined,
232
+ ): RootCallRow[] {
233
+ const rows = db.prepare(`SELECT c.id,c.repo_id repoId,r.name repoName,
234
+ r.workspace_id workspaceId,r.graph_generation graphGeneration,
235
+ c.source_symbol_id sourceSymbolId,c.source_file sourceFile,
236
+ c.call_type callType,c.event_name_expr eventName
237
+ FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
238
+ WHERE r.workspace_id=? AND (? IS NULL OR c.repo_id=?)
239
+ ORDER BY r.name COLLATE BINARY,r.id,c.source_file COLLATE BINARY,
240
+ c.call_site_start_offset,c.call_site_end_offset,c.source_line,c.id`).all(
241
+ workspaceId, repoId, repoId,
242
+ ) as unknown as RootCallRow[];
243
+ return files ? rows.filter((row) => files.has(row.sourceFile)) : rows;
244
+ }
245
+
246
+ function hasExactDispatch(db: Db, calls: RootCallRow[]): boolean {
247
+ const match = db.prepare(`SELECT 1 matched FROM graph_edges emitted
248
+ WHERE emitted.workspace_id=? AND emitted.generation=?
249
+ AND emitted.edge_type='HANDLER_EMITS_EVENT'
250
+ AND emitted.from_kind='call' AND emitted.from_id=?
251
+ AND EXISTS (SELECT 1 FROM graph_edges subscriber
252
+ WHERE subscriber.workspace_id=emitted.workspace_id
253
+ AND subscriber.generation=emitted.generation
254
+ AND subscriber.edge_type='EVENT_SUBSCRIPTION_HANDLED_BY'
255
+ AND subscriber.from_kind='event'
256
+ AND subscriber.from_id COLLATE BINARY=? COLLATE BINARY)
257
+ LIMIT 1`);
258
+ return calls.some((call) => call.callType === 'async_emit'
259
+ && typeof call.eventName === 'string'
260
+ && Boolean(match.get(call.workspaceId, call.graphGeneration,
261
+ String(call.id), call.eventName)));
262
+ }
263
+
264
+ function workspaceAmbiguityDiagnostic(db: Db): Record<string, unknown> {
265
+ const total = Number(db.prepare(`SELECT COUNT(DISTINCT w.id) count
266
+ FROM workspaces w JOIN repositories r ON r.workspace_id=w.id`).get()?.count ?? 0);
267
+ const workspaceIds = db.prepare(`SELECT DISTINCT w.id FROM workspaces w
268
+ JOIN repositories r ON r.workspace_id=w.id ORDER BY w.id LIMIT 5`).all()
269
+ .flatMap((row) => typeof row.id === 'number' ? [row.id] : []);
270
+ return {
271
+ severity: 'error', code: 'trace_workspace_ambiguous',
272
+ message: total > 1
273
+ ? 'Trace spans multiple indexed workspaces; provide a workspace identity.'
274
+ : 'No indexed workspace could be selected for this trace.',
275
+ workspaceCount: total, workspaceIds,
276
+ omittedWorkspaceCount: Math.max(0, total - workspaceIds.length),
277
+ remediation: 'Pass TraceOptions.workspaceId or select a repository in one workspace.',
278
+ };
279
+ }
@@ -0,0 +1,347 @@
1
+ import type { Db } from '../db/connection.js';
2
+ import type {
3
+ DynamicMode,
4
+ TraceOptions,
5
+ TraceResult,
6
+ TraceStart,
7
+ } from '../types.js';
8
+
9
+ export type CompactStatus =
10
+ | 'resolved'
11
+ | 'terminal'
12
+ | 'inferred'
13
+ | 'dynamic'
14
+ | 'ambiguous'
15
+ | 'unresolved'
16
+ | 'cycle';
17
+
18
+ export type CompactEndpointSide = 'source' | 'target';
19
+
20
+ export type CompactSemanticEndpoint =
21
+ | { kind: 'operation'; operationId: number }
22
+ | { kind: 'symbol'; symbolId: number }
23
+ | { kind: 'handler_method'; handlerMethodId: number }
24
+ | { kind: 'event'; workspaceId: number; eventName: string }
25
+ | {
26
+ kind: 'target';
27
+ workspaceId: number;
28
+ repositoryId?: number;
29
+ targetKind: string;
30
+ targetId: string;
31
+ }
32
+ | {
33
+ kind: 'call_site';
34
+ workspaceId: number;
35
+ repositoryId: number;
36
+ repositoryName: string;
37
+ sourceFile: string;
38
+ sourceLine: number;
39
+ startOffset?: number;
40
+ endOffset?: number;
41
+ callId: number;
42
+ }
43
+ | {
44
+ kind: 'scope';
45
+ workspaceId: number;
46
+ repositoryId?: number;
47
+ sourceFiles: string[];
48
+ symbolIds: number[];
49
+ structuralKey: string;
50
+ }
51
+ | {
52
+ kind: 'unavailable';
53
+ side: CompactEndpointSide;
54
+ endpointKind: string;
55
+ detailedEdgeIndex: number;
56
+ site?: CompactSourceSite;
57
+ };
58
+
59
+ export type CompactRemediationCode =
60
+ | 'provide_runtime_variables'
61
+ | 'select_implementation'
62
+ | 'reindex_and_link'
63
+ | 'inspect_detailed_edge';
64
+
65
+ export interface CompactDecisionTargetInput {
66
+ kind: string;
67
+ id: string;
68
+ }
69
+
70
+ export interface CompactDecisionInput {
71
+ effectiveResolutionStatus?: string;
72
+ effectiveTarget?: CompactDecisionTargetInput;
73
+ persistedResolutionStatus?: string;
74
+ persistedTarget?: CompactDecisionTargetInput;
75
+ missingVariableNames?: string[];
76
+ missingVariableCount?: number;
77
+ dynamicMode?: DynamicMode;
78
+ candidateCount?: number;
79
+ viableCandidateCount?: number;
80
+ rejectedCandidateCount?: number;
81
+ omittedCandidateCount?: number;
82
+ implementationStrategy?: string;
83
+ implementationGuided?: boolean;
84
+ implementationContextual?: boolean;
85
+ reasonCode?: string;
86
+ eventMatchStrategy?: string;
87
+ dispatchCertainty?: string;
88
+ eventSubscriptionCount?: number;
89
+ associationStatus?: string;
90
+ associationBasis?: string;
91
+ eventScope?: string;
92
+ callRole?: string;
93
+ factOrigin?: string;
94
+ roleSiteMatchCount?: number;
95
+ bodyExpansion?: string;
96
+ remediationCode?: CompactRemediationCode;
97
+ remediationHintCount?: number;
98
+ }
99
+
100
+ export interface CompactReferenceInput {
101
+ graphEdgeIds?: Array<number | string>;
102
+ outboundCallIds?: Array<number | string>;
103
+ subscribeCallIds?: Array<number | string>;
104
+ symbolCallIds?: Array<number | string>;
105
+ operationIds?: Array<number | string>;
106
+ symbolIds?: Array<number | string>;
107
+ handlerMethodIds?: Array<number | string>;
108
+ }
109
+
110
+ export interface CompactSourceSite {
111
+ repository?: string;
112
+ sourceFile?: string;
113
+ sourceLine?: number;
114
+ startOffset?: number;
115
+ endOffset?: number;
116
+ }
117
+
118
+ export interface CompactEdgeObservation {
119
+ ordinal: number;
120
+ step: number;
121
+ type: string;
122
+ source: CompactSemanticEndpoint;
123
+ target: CompactSemanticEndpoint;
124
+ status: CompactStatus;
125
+ confidence: number;
126
+ decision?: CompactDecisionInput;
127
+ refs?: CompactReferenceInput;
128
+ site?: CompactSourceSite;
129
+ }
130
+
131
+ export interface CompactTraceObserver {
132
+ record(observation: CompactEdgeObservation): void;
133
+ setWorkspaceId?(workspaceId: number | undefined): void;
134
+ }
135
+
136
+ export class CompactObservationCollector implements CompactTraceObserver {
137
+ readonly observations: CompactEdgeObservation[] = [];
138
+ workspaceId?: number;
139
+
140
+ record(observation: CompactEdgeObservation): void {
141
+ this.observations.push(observation);
142
+ }
143
+
144
+ setWorkspaceId(workspaceId: number | undefined): void {
145
+ this.workspaceId = workspaceId;
146
+ }
147
+ }
148
+
149
+ export interface CompactSourceContext {
150
+ schemaVersion: number;
151
+ analyzerVersion: string;
152
+ graphGeneration: number;
153
+ }
154
+
155
+ export interface CompactProjectionInput {
156
+ db: Db;
157
+ start: TraceStart;
158
+ options: TraceOptions;
159
+ source: CompactSourceContext;
160
+ trace: TraceResult;
161
+ observations: CompactEdgeObservation[];
162
+ }
163
+
164
+ export interface CompactHintV1 {
165
+ servicePath: string | null;
166
+ operationPath: string | null;
167
+ packageName: string | null;
168
+ repositoryName: string | null;
169
+ candidateFamily: string | null;
170
+ implementationRepo: string | null;
171
+ }
172
+
173
+ export interface CompactStartV1 {
174
+ repo: string | null;
175
+ servicePath: string | null;
176
+ operation: string | null;
177
+ operationPath: string | null;
178
+ handler: string | null;
179
+ }
180
+
181
+ export interface CompactQueryV1 {
182
+ depth: number;
183
+ includeAsync: boolean;
184
+ includeDb: boolean;
185
+ includeExternal: boolean;
186
+ dynamicMode: DynamicMode;
187
+ maxDynamicCandidates: number;
188
+ suppliedVariableNames: string[];
189
+ runtimeValuesOmitted: true;
190
+ implementationRepo: string | null;
191
+ implementationHints: CompactHintV1[];
192
+ }
193
+
194
+ export interface CompactReferenceGroupV1 {
195
+ values: Array<number | string>;
196
+ total: number;
197
+ shown: number;
198
+ omitted: number;
199
+ }
200
+
201
+ export interface CompactReferencesV1 {
202
+ graphEdgeIds?: CompactReferenceGroupV1;
203
+ outboundCallIds?: CompactReferenceGroupV1;
204
+ subscribeCallIds?: CompactReferenceGroupV1;
205
+ symbolCallIds?: CompactReferenceGroupV1;
206
+ operationIds?: CompactReferenceGroupV1;
207
+ symbolIds?: CompactReferenceGroupV1;
208
+ handlerMethodIds?: CompactReferenceGroupV1;
209
+ }
210
+
211
+ export interface CompactDecisionV1 {
212
+ effectiveResolutionStatus?: string;
213
+ effectiveTarget?: string;
214
+ persistedResolutionStatus?: string;
215
+ persistedTarget?: string;
216
+ missingVariableNames?: string[];
217
+ missingVariableCount?: number;
218
+ shownMissingVariableCount?: number;
219
+ omittedMissingVariableCount?: number;
220
+ dynamicMode?: DynamicMode;
221
+ candidateCount?: number;
222
+ viableCandidateCount?: number;
223
+ rejectedCandidateCount?: number;
224
+ omittedCandidateCount?: number;
225
+ implementationStrategy?: string;
226
+ implementationGuided?: boolean;
227
+ implementationContextual?: boolean;
228
+ eventMatchStrategy?: string;
229
+ dispatchCertainty?: string;
230
+ eventSubscriptionCount?: number;
231
+ associationStatus?: string;
232
+ associationBasis?: string;
233
+ eventScope?: string;
234
+ callRole?: string;
235
+ factOrigin?: string;
236
+ roleSiteMatchCount?: number;
237
+ bodyExpansion?: string;
238
+ reasonCode?: string;
239
+ remediationHint?: string;
240
+ omittedRemediationHintCount?: number;
241
+ }
242
+
243
+ export interface CompactEdgeDetailsV1 {
244
+ decision: CompactDecisionV1;
245
+ refs: CompactReferencesV1;
246
+ }
247
+
248
+ export interface CompactDiagnosticDetailsV1 {
249
+ reasonCode?: string;
250
+ missingVariableNames?: string[];
251
+ missingVariableCount?: number;
252
+ shownMissingVariableCount?: number;
253
+ omittedMissingVariableCount?: number;
254
+ candidateCount?: number;
255
+ viableCandidateCount?: number;
256
+ rejectedCandidateCount?: number;
257
+ remediationHint?: string;
258
+ omittedHintCount?: number;
259
+ }
260
+
261
+ export interface CompactProjectedDiagnostic {
262
+ index: number;
263
+ severity: 'error' | 'warning' | 'info';
264
+ code: string;
265
+ message: string;
266
+ file?: string;
267
+ line?: number;
268
+ details?: CompactDiagnosticDetailsV1;
269
+ }
270
+
271
+ export type CompactNodeRowV1 = [
272
+ id: string,
273
+ kind: string,
274
+ label: string,
275
+ repo: number | null,
276
+ file: number | null,
277
+ line: number | null,
278
+ ];
279
+
280
+ export type CompactEdgeRowV1 = [
281
+ id: string,
282
+ traceOrdinals: number[],
283
+ step: number,
284
+ type: string,
285
+ from: string,
286
+ to: string,
287
+ status: CompactStatus,
288
+ confidence: number,
289
+ count: number,
290
+ details: CompactEdgeDetailsV1 | null,
291
+ ];
292
+
293
+ export type CompactDiagnosticRowV1 = [
294
+ fullDiagnosticIndex: number,
295
+ severity: 'error' | 'warning' | 'info',
296
+ code: string,
297
+ message: string,
298
+ file: number | null,
299
+ line: number | null,
300
+ details: CompactDiagnosticDetailsV1 | null,
301
+ ];
302
+
303
+ export interface CompactStatusCountsV1 {
304
+ resolved: number;
305
+ terminal: number;
306
+ inferred: number;
307
+ dynamic: number;
308
+ ambiguous: number;
309
+ unresolved: number;
310
+ cycle: number;
311
+ }
312
+
313
+ export interface CompactGraphV1 {
314
+ schema: 'service-flow/compact-graph@1';
315
+ start: CompactStartV1;
316
+ query: CompactQueryV1;
317
+ source: CompactSourceContext;
318
+ summary: {
319
+ completeness: 'complete' | 'partial' | 'blocked';
320
+ fullTraceNodes: number;
321
+ fullTraceEdges: number;
322
+ fullTraceDiagnostics: number;
323
+ nodes: number;
324
+ edges: number;
325
+ collapsedEdges: number;
326
+ statusCounts: CompactStatusCountsV1;
327
+ projection: {
328
+ evidence: 'summary-only';
329
+ syntheticEndpoints: number;
330
+ omittedUnreferencedFullNodes: number;
331
+ };
332
+ };
333
+ repos: string[];
334
+ files: string[];
335
+ nodeColumns: ['id', 'kind', 'label', 'repo', 'file', 'line'];
336
+ nodes: CompactNodeRowV1[];
337
+ edgeColumns: [
338
+ 'id', 'traceOrdinals', 'step', 'type', 'from', 'to',
339
+ 'status', 'confidence', 'count', 'details',
340
+ ];
341
+ edges: CompactEdgeRowV1[];
342
+ diagnosticColumns: [
343
+ 'fullDiagnosticIndex', 'severity', 'code', 'message',
344
+ 'file', 'line', 'details',
345
+ ];
346
+ diagnostics: CompactDiagnosticRowV1[];
347
+ }