@saptools/service-flow 0.1.72 → 0.1.73

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 (41) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +19 -6
  3. package/TECHNICAL-NOTE.md +11 -2
  4. package/dist/{chunk-Z6D433R5.js → chunk-32WOTGTS.js} +6714 -6161
  5. package/dist/chunk-32WOTGTS.js.map +1 -0
  6. package/dist/cli.js +189 -39
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +2 -1
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/doctor-event-quality.ts +90 -15
  12. package/src/cli.ts +35 -13
  13. package/src/config/workspace-config.ts +15 -0
  14. package/src/db/current-fact-semantics.ts +2 -26
  15. package/src/db/event-fact-semantics.ts +260 -61
  16. package/src/db/event-site-semantics.ts +62 -0
  17. package/src/db/fact-lifecycle.ts +70 -12
  18. package/src/db/migrations.ts +4 -1
  19. package/src/db/package-target-invalidation.ts +4 -3
  20. package/src/db/schema.ts +1 -1
  21. package/src/indexer/repository-indexer.ts +50 -6
  22. package/src/indexer/workspace-indexer.ts +13 -3
  23. package/src/linker/cross-repo-linker.ts +4 -2
  24. package/src/linker/event-shape-candidate-linker.ts +63 -20
  25. package/src/linker/event-subscription-handler-linker.ts +121 -30
  26. package/src/linker/package-event-constant-resolver.ts +22 -18
  27. package/src/output/repository-inspection.ts +11 -0
  28. package/src/parsers/environment-declarations.ts +104 -27
  29. package/src/parsers/event-call-analysis.ts +163 -35
  30. package/src/parsers/event-environment-reference.ts +18 -7
  31. package/src/parsers/event-receiver-analysis.ts +17 -27
  32. package/src/parsers/outbound-call-classifier.ts +1 -1
  33. package/src/parsers/stable-local-value.ts +12 -1
  34. package/src/parsers/string-constant-lookups.ts +78 -28
  35. package/src/trace/edge-target.ts +65 -0
  36. package/src/trace/event-subscriber-traversal.ts +71 -1
  37. package/src/trace/evidence.ts +0 -28
  38. package/src/trace/trace-scope-execution.ts +1 -1
  39. package/src/types.ts +3 -1
  40. package/src/version.ts +1 -1
  41. package/dist/chunk-Z6D433R5.js.map +0 -1
@@ -8,7 +8,10 @@ import {
8
8
  type SymbolImportBinding,
9
9
  } from './symbol-import-bindings.js';
10
10
  import type { RepositorySourceContext } from './ts-project.js';
11
- import { EVENT_ENVIRONMENT_KEY_ALLOWLIST } from
11
+ import {
12
+ DEFAULT_EVENT_ENVIRONMENT_KEYS,
13
+ normalizeEventEnvironmentKeys,
14
+ } from
12
15
  './environment-declarations.js';
13
16
 
14
17
  export type EventEnvironmentTransform = 'toUpperCase' | 'toLowerCase';
@@ -34,13 +37,10 @@ interface ResolutionContext {
34
37
  sourceFile: string;
35
38
  depth: number;
36
39
  seen: Set<string>;
40
+ allowedKeys: ReadonlySet<string>;
37
41
  }
38
42
 
39
43
  const maxEnvironmentReferenceDepth = 6;
40
- const allowedEnvironmentKeys = new Set<string>(
41
- EVENT_ENVIRONMENT_KEY_ALLOWLIST,
42
- );
43
-
44
44
  function unwrapExpression(expression: ts.Expression): ts.Expression {
45
45
  if (ts.isParenthesizedExpression(expression)
46
46
  || ts.isAsExpression(expression)
@@ -173,6 +173,15 @@ function identifierReference(
173
173
  const local = resolveBinding(identifier, identifier);
174
174
  if (local.declaration && local.initializer && local.immutable)
175
175
  return resolveEnvironmentReference(local.initializer, context);
176
+ const binding = bindingFor(
177
+ identifier, collectSymbolImportBindings(context.source),
178
+ );
179
+ if (binding?.moduleKind === 'package') return {
180
+ status: 'refused',
181
+ sourceKey: identifier.getText(context.source),
182
+ transforms: [],
183
+ reason: 'event_environment_package_import_unsupported',
184
+ };
176
185
  const imported = importedIdentifier(identifier, context);
177
186
  if (!imported) return undefined;
178
187
  return resolveEnvironmentReference(imported.initializer, {
@@ -199,7 +208,7 @@ function resolveEnvironmentReference(
199
208
  const next = { ...context, depth: context.depth + 1, seen };
200
209
  const value = unwrapExpression(expression);
201
210
  const key = processEnvironmentKey(value);
202
- if (key && allowedEnvironmentKeys.has(key)) return {
211
+ if (key && context.allowedKeys.has(key)) return {
203
212
  status: 'resolved', sourceKey, environmentKey: key, transforms: [],
204
213
  sourceFile: context.sourceFile,
205
214
  startOffset: value.getStart(context.source), endOffset: value.getEnd(),
@@ -215,9 +224,11 @@ export function createEventEnvironmentReferenceResolver(
215
224
  sources: RepositorySourceContext,
216
225
  source: ts.SourceFile,
217
226
  sourceFile: string,
227
+ configuredKeys: readonly string[] = DEFAULT_EVENT_ENVIRONMENT_KEYS,
218
228
  ): EventEnvironmentReferenceResolver {
229
+ const allowedKeys = new Set(normalizeEventEnvironmentKeys(configuredKeys));
219
230
  return (expression) => resolveEnvironmentReference(expression, {
220
- sources, source, sourceFile, depth: 0, seen: new Set(),
231
+ sources, source, sourceFile, depth: 0, seen: new Set(), allowedKeys,
221
232
  });
222
233
  }
223
234
 
@@ -23,7 +23,7 @@ export type EventReceiverUnresolvedReason =
23
23
  export interface EventReceiverEvidenceSite {
24
24
  startOffset: number;
25
25
  endOffset: number;
26
- flow: BindingLexicalSite['flow'];
26
+ flow: BindingLexicalSite['flow'] | 'reference';
27
27
  connect: boolean;
28
28
  }
29
29
 
@@ -34,6 +34,7 @@ export interface EventReceiverProof {
34
34
  receiverClassification: 'cap_evidence' | 'name_fallback' | 'unproven';
35
35
  receiverProof: string;
36
36
  unresolvedReason?: EventReceiverUnresolvedReason;
37
+ fallbackRefusedReason?: string;
37
38
  consideredBindingSites: EventReceiverEvidenceSite[];
38
39
  }
39
40
 
@@ -41,7 +42,6 @@ export interface EventReceiverIndex {
41
42
  source: ts.SourceFile;
42
43
  lexical: BindingLexicalIndex;
43
44
  imports: SymbolImportBinding[];
44
- compatibilityNames: Set<string>;
45
45
  }
46
46
 
47
47
  const eventReceiverNames = new Set([
@@ -218,17 +218,6 @@ function sameExecutionScope(
218
218
  return sameScope(left, right);
219
219
  }
220
220
 
221
- function unsupportedBranch(node: ts.Node): boolean {
222
- let current: ts.Node | undefined = node.parent;
223
- while (current && !ts.isSourceFile(current) && !ts.isFunctionLike(current)) {
224
- if (ts.isIfStatement(current) || ts.isConditionalExpression(current)
225
- || ts.isSwitchStatement(current)
226
- || ts.isIterationStatement(current, false)) return true;
227
- current = current.parent;
228
- }
229
- return false;
230
- }
231
-
232
221
  function reachingSites(
233
222
  index: EventReceiverIndex,
234
223
  declaration: BindingLexicalSite,
@@ -275,11 +264,6 @@ function lexicalProof(
275
264
  return value ? [value] : [];
276
265
  });
277
266
  const considered = evidenceSites(sites, identifier.text, index.imports);
278
- if (sites.some((site) => unsupportedBranch(site.node)))
279
- return unproven(
280
- 'event_receiver_unproven_binding', 'branch_dependent_assignment',
281
- considered,
282
- );
283
267
  if (expressions.length > 0
284
268
  && expressions.every((value) => isCapConnect(value, index.imports)))
285
269
  return {
@@ -313,15 +297,24 @@ function unproven(
313
297
  }
314
298
 
315
299
  function compatibilityFallback(
316
- name: string,
317
- index: EventReceiverIndex,
300
+ identifier: ts.Identifier,
301
+ refusedReason: string,
302
+ consideredBindingSites: EventReceiverEvidenceSite[],
318
303
  ): Omit<EventReceiverProof, 'effectiveReceiver' | 'receiver'
319
304
  | 'rootReceiver'> | undefined {
320
- return eventReceiverNames.has(name) || index.compatibilityNames.has(name)
305
+ return eventReceiverNames.has(identifier.text)
321
306
  ? {
322
307
  receiverClassification: 'name_fallback',
323
308
  receiverProof: 'compatibility_name_fallback',
324
- consideredBindingSites: [],
309
+ fallbackRefusedReason: refusedReason,
310
+ consideredBindingSites: consideredBindingSites.length > 0
311
+ ? consideredBindingSites
312
+ : [{
313
+ startOffset: identifier.getStart(identifier.getSourceFile()),
314
+ endOffset: identifier.getEnd(),
315
+ flow: 'reference',
316
+ connect: false,
317
+ }],
325
318
  }
326
319
  : undefined;
327
320
  }
@@ -344,9 +337,8 @@ function identifierProof(
344
337
  consideredBindingSites: [],
345
338
  };
346
339
  const proven = lexicalProof(identifier, use, index);
347
- if (proven?.receiverClassification === 'cap_evidence') return proven;
348
- return compatibilityFallback(identifier.text, index)
349
- ?? proven
340
+ return proven
341
+ ?? compatibilityFallback(identifier, 'binding_not_found', [])
350
342
  ?? unproven('event_receiver_unproven_binding', 'binding_not_found');
351
343
  }
352
344
 
@@ -364,13 +356,11 @@ function rootIdentifier(
364
356
 
365
357
  export function createEventReceiverIndex(
366
358
  source: ts.SourceFile,
367
- compatibilityNames: Set<string>,
368
359
  ): EventReceiverIndex {
369
360
  return {
370
361
  source,
371
362
  lexical: createBindingLexicalIndex(source),
372
363
  imports: collectSymbolImportBindings(source),
373
- compatibilityNames,
374
364
  };
375
365
  }
376
366
 
@@ -566,7 +566,7 @@ export function classifyOutboundCallsInSource(
566
566
  const initializers = variableInitializers(source);
567
567
  const serviceVariables = collectServiceVariables(source);
568
568
  const eventAnalysis = createEventCallAnalysisContext(
569
- source, serviceVariables, options.importedEventNameResolver,
569
+ source, options.importedEventNameResolver,
570
570
  options.eventEnvironmentReferenceResolver,
571
571
  );
572
572
  const wrapperSpecs = collectWrapperSpecs(source);
@@ -205,6 +205,16 @@ function inertUnaryUse(node: ts.Node): boolean {
205
205
  || ts.isTypeOfExpression(parent)) && parent.expression === node));
206
206
  }
207
207
 
208
+ function typePositionUse(node: ts.Node): boolean {
209
+ let current: ts.Node | undefined = node.parent;
210
+ while (current && !ts.isStatement(current)
211
+ && !ts.isSourceFile(current)) {
212
+ if (ts.isTypeNode(current)) return true;
213
+ current = current.parent;
214
+ }
215
+ return false;
216
+ }
217
+
208
218
  function safeReferenceUse(
209
219
  node: ts.Identifier,
210
220
  declaration: ts.Identifier,
@@ -213,7 +223,8 @@ function safeReferenceUse(
213
223
  return memberReceiverUse(use, declaration)
214
224
  || commonJsExportUse(use)
215
225
  || esmExportUse(use)
216
- || inertUnaryUse(use);
226
+ || inertUnaryUse(use)
227
+ || typePositionUse(use);
217
228
  }
218
229
 
219
230
  export function stableLocalValueReference(
@@ -33,7 +33,9 @@ export interface StaticStringRefusal {
33
33
  exported: boolean;
34
34
  stable: boolean;
35
35
  reason: 'event_name_constant_member_not_string'
36
- | 'event_name_constant_container_mutable';
36
+ | 'event_name_constant_container_mutable'
37
+ | 'event_name_constant_container_unsafe_reference'
38
+ | 'event_name_constant_container_unsupported_shape';
37
39
  }
38
40
 
39
41
  export interface StringConstantLookups {
@@ -144,7 +146,7 @@ function collectEnum(
144
146
  source, key, member, {
145
147
  kind: 'enum_member', containerName: statement.name.text,
146
148
  memberName, exported, stable,
147
- reason: 'event_name_constant_container_mutable',
149
+ reason: 'event_name_constant_container_unsafe_reference',
148
150
  },
149
151
  ));
150
152
  else if (!value) lookups.refusedMembers.set(key, refusalFact(
@@ -173,6 +175,44 @@ function objectLiteral(
173
175
  return ts.isObjectLiteralExpression(value) ? value : undefined;
174
176
  }
175
177
 
178
+ function objectMemberName(
179
+ property: ts.ObjectLiteralElementLike,
180
+ ): string | undefined {
181
+ if (ts.isSpreadAssignment(property)) return undefined;
182
+ return property.name ? propertyName(property.name) : undefined;
183
+ }
184
+
185
+ function supportedObjectShape(object: ts.ObjectLiteralExpression): boolean {
186
+ return object.properties.every((property) =>
187
+ ts.isPropertyAssignment(property)
188
+ && propertyName(property.name) !== undefined);
189
+ }
190
+
191
+ function refuseObjectShape(
192
+ source: ts.SourceFile,
193
+ object: ts.ObjectLiteralExpression,
194
+ declaration: ts.VariableDeclaration,
195
+ exported: boolean,
196
+ lookups: StringConstantLookups,
197
+ ): void {
198
+ if (!ts.isIdentifier(declaration.name)) return;
199
+ for (const property of object.properties) {
200
+ const memberName = objectMemberName(property);
201
+ if (!memberName) continue;
202
+ const key = `${declaration.name.text}.${memberName}`;
203
+ lookups.refusedMembers.set(key, refusalFact(
204
+ source, key, property, {
205
+ kind: 'const_object_property',
206
+ containerName: declaration.name.text,
207
+ memberName,
208
+ exported,
209
+ stable: false,
210
+ reason: 'event_name_constant_container_unsupported_shape',
211
+ },
212
+ ));
213
+ }
214
+ }
215
+
176
216
  function collectObject(
177
217
  source: ts.SourceFile,
178
218
  statement: ts.VariableStatement,
@@ -186,38 +226,48 @@ function collectObject(
186
226
  const stable = stableLocalValueReference(source, declaration.name);
187
227
  const exported = hasExportModifier(statement)
188
228
  || exports.has(declaration.name.text);
229
+ if (!supportedObjectShape(object)) {
230
+ refuseObjectShape(source, object, declaration, exported, lookups);
231
+ return;
232
+ }
189
233
  for (const property of object.properties) {
190
- if (!ts.isPropertyAssignment(property)) continue;
191
- const memberName = propertyName(property.name);
192
- if (!memberName) continue;
193
- const key = `${declaration.name.text}.${memberName}`;
194
- const value = stringValue(property.initializer);
195
- if (!stable) lookups.refusedMembers.set(key, refusalFact(
196
- source, key, property, {
197
- kind: 'const_object_property',
198
- containerName: declaration.name.text, memberName,
199
- exported, stable,
200
- reason: 'event_name_constant_container_mutable',
201
- },
202
- ));
203
- else if (!value) lookups.refusedMembers.set(key, refusalFact(
234
+ if (ts.isPropertyAssignment(property))
235
+ collectObjectProperty(
236
+ source, property, declaration.name.text, exported, stable, lookups,
237
+ );
238
+ }
239
+ }
240
+
241
+ function collectObjectProperty(
242
+ source: ts.SourceFile,
243
+ property: ts.PropertyAssignment,
244
+ containerName: string,
245
+ exported: boolean,
246
+ stable: boolean,
247
+ lookups: StringConstantLookups,
248
+ ): void {
249
+ const memberName = propertyName(property.name);
250
+ if (!memberName) return;
251
+ const key = `${containerName}.${memberName}`;
252
+ const value = stringValue(property.initializer);
253
+ if (!stable || !value) {
254
+ lookups.refusedMembers.set(key, refusalFact(
204
255
  source, key, property, {
205
- kind: 'const_object_property',
206
- containerName: declaration.name.text, memberName,
256
+ kind: 'const_object_property', containerName, memberName,
207
257
  exported, stable,
208
- reason: 'event_name_constant_member_not_string',
209
- },
210
- ));
211
- else lookups.objectProperties.set(key, staticFact(
212
- source, key, value, property, {
213
- kind: 'const_object_property',
214
- containerName: declaration.name.text,
215
- memberName,
216
- exported,
217
- stable,
258
+ reason: stable
259
+ ? 'event_name_constant_member_not_string'
260
+ : 'event_name_constant_container_unsafe_reference',
218
261
  },
219
262
  ));
263
+ return;
220
264
  }
265
+ lookups.objectProperties.set(key, staticFact(
266
+ source, key, value, property, {
267
+ kind: 'const_object_property', containerName, memberName,
268
+ exported, stable,
269
+ },
270
+ ));
221
271
  }
222
272
 
223
273
  function collectIdentifier(
@@ -0,0 +1,65 @@
1
+ import type { TraceGraphRow } from './evidence.js';
2
+
3
+ function record(value: unknown): Record<string, unknown> {
4
+ return value && typeof value === 'object' && !Array.isArray(value)
5
+ ? value as Record<string, unknown> : {};
6
+ }
7
+
8
+ function text(value: unknown): string | undefined {
9
+ return typeof value === 'string' ? value : undefined;
10
+ }
11
+
12
+ function effectiveTarget(
13
+ evidence: Record<string, unknown>,
14
+ ): string | undefined {
15
+ const effective = record(evidence.effectiveResolution);
16
+ const service = text(
17
+ effective.targetServicePath ?? evidence.targetServicePath,
18
+ );
19
+ const operation = text(
20
+ effective.targetOperationPath ?? evidence.targetOperationPath,
21
+ );
22
+ return service && operation ? `${service}${operation}` : undefined;
23
+ }
24
+
25
+ function runtimeCandidateTarget(
26
+ evidence: Record<string, unknown>,
27
+ ): string | undefined {
28
+ const candidate = record(evidence.runtimeResolvedCandidate);
29
+ const service = text(candidate.servicePath);
30
+ const operation = text(candidate.operationPath);
31
+ return service && operation ? `${service}${operation}` : undefined;
32
+ }
33
+
34
+ function semanticEdgeTarget(
35
+ row: TraceGraphRow,
36
+ evidence: Record<string, unknown>,
37
+ ): string | undefined {
38
+ if (row.edge_type === 'HANDLER_RUNS_DB_QUERY')
39
+ return `Entity: ${row.to_id || 'unknown'}`;
40
+ if (row.edge_type === 'HANDLER_RUNS_REMOTE_QUERY')
41
+ return text(evidence.remoteQueryTarget)
42
+ ?? `Remote query: ${row.to_id || 'unknown'}`;
43
+ if (row.edge_type === 'HANDLER_CALLS_EXTERNAL_HTTP') {
44
+ const target = record(evidence.externalTarget);
45
+ return text(target.label) ?? `External endpoint: ${row.to_id || 'unknown'}`;
46
+ }
47
+ return row.edge_type === 'EVENT_SHAPE_CANDIDATE_SUBSCRIBER'
48
+ ? text(evidence.eventShapeCandidateTargetLabel) : undefined;
49
+ }
50
+
51
+ export function edgeTarget(
52
+ row: TraceGraphRow,
53
+ evidence: Record<string, unknown>,
54
+ ): string {
55
+ const exact = effectiveTarget(evidence) ?? runtimeCandidateTarget(evidence);
56
+ if (exact) return exact;
57
+ const semantic = semanticEdgeTarget(row, evidence);
58
+ if (semantic) return semantic;
59
+ const service = text(evidence.servicePath);
60
+ const operation = text(evidence.operationPath);
61
+ if (service && operation) return `${service}${operation}`;
62
+ const targetOperation = text(evidence.targetOperation);
63
+ const targetRepo = text(evidence.targetRepo) ?? '';
64
+ return targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
65
+ }
@@ -64,9 +64,23 @@ export interface EventSubscriberTransition {
64
64
  omittedSymbolCallUnresolvedReasonCharacterCount?: number;
65
65
  matchStrategy?: string;
66
66
  dispatchCertainty?: string;
67
+ subscriptionConsumerRepoId?: number;
68
+ subscriptionConsumerRepoName?: string;
69
+ dispatchProvenances?: EventDispatchProvenance[];
70
+ dispatchProvenanceCount?: number;
71
+ shownDispatchProvenanceCount?: number;
72
+ omittedDispatchProvenanceCount?: number;
67
73
  handler?: EventSubscriberSymbolTarget;
68
74
  }
69
75
 
76
+ export interface EventDispatchProvenance {
77
+ graphEdgeId: number;
78
+ matchStrategy: string;
79
+ dispatchCertainty: string;
80
+ consumerRepoId?: number;
81
+ consumerRepoName?: string;
82
+ }
83
+
70
84
  export interface EventSubscriberTransitionQuery {
71
85
  workspaceId: number;
72
86
  graphGeneration: number;
@@ -171,6 +185,10 @@ export function eventTransitionEvidence(
171
185
  eventName: transition.eventName,
172
186
  matchStrategy: transition.matchStrategy ?? 'workspace_exact_event_name',
173
187
  dispatchCertainty: transition.dispatchCertainty ?? 'static_name_only',
188
+ dispatchProvenances: transition.dispatchProvenances,
189
+ dispatchProvenanceCount: transition.dispatchProvenanceCount,
190
+ shownDispatchProvenanceCount: transition.shownDispatchProvenanceCount,
191
+ omittedDispatchProvenanceCount: transition.omittedDispatchProvenanceCount,
174
192
  associationBasis: transition.associationBasis,
175
193
  dispatchScope: transition.dispatchScope,
176
194
  roleSiteMatchCount: transition.roleSiteMatchCount,
@@ -239,11 +257,57 @@ export function loadEventSubscriberTransitions(
239
257
  subscribe.call_site_start_offset,subscribe.call_site_end_offset,ge.id`).all(
240
258
  query.workspaceId, query.graphGeneration,
241
259
  );
242
- return rows.flatMap((raw) => {
260
+ const transitions = rows.flatMap((raw) => {
243
261
  const row = eventRowForQuery(raw, query);
244
262
  const transition = row ? transitionFromRow(row) : undefined;
245
263
  return transition ? [transition] : [];
246
264
  });
265
+ return deduplicateDispatchProvenance(transitions);
266
+ }
267
+
268
+ const dispatchProvenanceLimit = 5;
269
+
270
+ function transitionIdentity(value: EventSubscriberTransition): string {
271
+ const subscription = value.subscribeCallId ?? `edge:${value.graphEdgeId}`;
272
+ return `${value.eventName}\0${subscription}\0${value.targetKind}\0${
273
+ value.targetId}\0${value.status}`;
274
+ }
275
+
276
+ function dispatchProvenance(
277
+ value: EventSubscriberTransition,
278
+ ): EventDispatchProvenance {
279
+ return {
280
+ graphEdgeId: value.graphEdgeId,
281
+ matchStrategy: value.matchStrategy ?? 'workspace_exact_event_name',
282
+ dispatchCertainty: value.dispatchCertainty ?? 'static_name_only',
283
+ consumerRepoId: value.subscriptionConsumerRepoId,
284
+ consumerRepoName: value.subscriptionConsumerRepoName,
285
+ };
286
+ }
287
+
288
+ function deduplicateDispatchProvenance(
289
+ values: EventSubscriberTransition[],
290
+ ): EventSubscriberTransition[] {
291
+ const groups = new Map<string, EventSubscriberTransition[]>();
292
+ for (const value of values) {
293
+ const key = transitionIdentity(value);
294
+ groups.set(key, [...(groups.get(key) ?? []), value]);
295
+ }
296
+ return [...groups.values()].map((group) => {
297
+ const first = group[0];
298
+ if (!first || group.length === 1) return first;
299
+ const provenances = group.map(dispatchProvenance).slice(
300
+ 0, dispatchProvenanceLimit,
301
+ );
302
+ return {
303
+ ...first,
304
+ dispatchProvenances: provenances,
305
+ dispatchProvenanceCount: group.length,
306
+ shownDispatchProvenanceCount: provenances.length,
307
+ omittedDispatchProvenanceCount: group.length - provenances.length,
308
+ };
309
+ }).filter((value): value is EventSubscriberTransition =>
310
+ value !== undefined);
247
311
  }
248
312
 
249
313
  function eventRowForQuery(
@@ -394,6 +458,12 @@ function associationEvidence(
394
458
  : undefined,
395
459
  matchStrategy: stringValue(evidence.matchStrategy),
396
460
  dispatchCertainty: stringValue(evidence.dispatchCertainty),
461
+ subscriptionConsumerRepoId: numberValue(
462
+ evidence.subscriptionConsumerRepositoryId,
463
+ ),
464
+ subscriptionConsumerRepoName: stringValue(
465
+ evidence.subscriptionConsumerRepositoryName,
466
+ ),
397
467
  };
398
468
  }
399
469
 
@@ -23,13 +23,6 @@ export interface TraceGraphRow extends Record<string, unknown> {
23
23
  status?: string;
24
24
  }
25
25
 
26
- interface Candidate {
27
- servicePath?: string;
28
- operationPath?: string;
29
- repoName?: string;
30
- operationName?: string;
31
- score?: number;
32
- }
33
26
  interface RuntimeDiagnosticTotals {
34
27
  missing: Set<string>;
35
28
  candidateCount: number;
@@ -342,27 +335,6 @@ export function runtimeNoCandidateDiagnostics(
342
335
  });
343
336
  }
344
337
 
345
- export function edgeTarget(row: TraceGraphRow, evidence: Record<string, unknown>): string {
346
- const effective = parseObject(evidence.effectiveResolution);
347
- const targetServicePath = stringValue(effective.targetServicePath ?? evidence.targetServicePath);
348
- const targetOperationPath = stringValue(effective.targetOperationPath ?? evidence.targetOperationPath);
349
- if (targetServicePath && targetOperationPath) return `${targetServicePath}${targetOperationPath}`;
350
- const runtimeCandidate = evidence.runtimeResolvedCandidate as Candidate | undefined;
351
- if (runtimeCandidate?.servicePath && runtimeCandidate.operationPath) return `${runtimeCandidate.servicePath}${runtimeCandidate.operationPath}`;
352
- const servicePath = stringValue(evidence.servicePath);
353
- const operationPath = stringValue(evidence.operationPath);
354
- const targetOperation = stringValue(evidence.targetOperation);
355
- const targetRepo = stringValue(evidence.targetRepo) ?? '';
356
- if (row.edge_type === 'HANDLER_RUNS_DB_QUERY') return `Entity: ${row.to_id || 'unknown'}`;
357
- if (row.edge_type === 'HANDLER_RUNS_REMOTE_QUERY') return stringValue(evidence.remoteQueryTarget) ?? `Remote query: ${row.to_id || 'unknown'}`;
358
- if (row.edge_type === 'HANDLER_CALLS_EXTERNAL_HTTP') {
359
- const target = parseObject(evidence.externalTarget);
360
- return stringValue(target.label) ?? `External endpoint: ${row.to_id || 'unknown'}`;
361
- }
362
- if (servicePath && operationPath) return `${servicePath}${operationPath}`;
363
- return targetOperation ? `${targetRepo}:${targetOperation}` : row.to_id;
364
- }
365
-
366
338
  function persistedResolution(row: TraceGraphRow): Record<string, unknown> {
367
339
  return {
368
340
  status: row.status,
@@ -2,10 +2,10 @@ import type { Db } from '../db/connection.js';
2
2
  import type { TraceEdge, TraceOptions } from '../types.js';
3
3
  import {
4
4
  baseTraceEvidence,
5
- edgeTarget,
6
5
  runtimeResolution,
7
6
  type TraceGraphRow,
8
7
  } from './evidence.js';
8
+ import { edgeTarget } from './edge-target.js';
9
9
  import { dynamicCandidateBranches } from './dynamic-branches.js';
10
10
  import {
11
11
  contextualRuntimeResolution,
package/src/types.ts CHANGED
@@ -286,7 +286,9 @@ export interface GeneratedConstantFact {
286
286
  stable: boolean;
287
287
  resolutionStatus: 'resolved' | 'refused';
288
288
  unresolvedReason?: 'event_name_constant_member_not_string'
289
- | 'event_name_constant_container_mutable';
289
+ | 'event_name_constant_container_mutable'
290
+ | 'event_name_constant_container_unsafe_reference'
291
+ | 'event_name_constant_container_unsupported_shape';
290
292
  declarationStartOffset: number;
291
293
  declarationEndOffset: number;
292
294
  valueStartOffset: number;
package/src/version.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import packageJson from '../package.json' with { type: 'json' };
2
2
 
3
3
  export const VERSION = packageJson.version;
4
- export const ANALYZER_VERSION = '0.1.71-facts.1';
4
+ export const ANALYZER_VERSION = '0.1.73-facts.1';