@saptools/service-flow 0.1.69 → 0.1.72

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 (144) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +9 -5
  3. package/TECHNICAL-NOTE.md +17 -0
  4. package/dist/{chunk-3N3B5KHV.js → chunk-Z6D433R5.js} +8766 -6546
  5. package/dist/chunk-Z6D433R5.js.map +1 -0
  6. package/dist/cli.js +703 -160
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +53 -14
  9. package/dist/index.js +2 -23
  10. package/dist/index.js.map +1 -1
  11. package/package.json +1 -1
  12. package/src/cli/doctor-event-quality.ts +371 -0
  13. package/src/cli/{002-doctor-lifecycle.ts → doctor-lifecycle.ts} +1 -1
  14. package/src/cli/{001-doctor-projection.ts → doctor-projection.ts} +1 -1
  15. package/src/cli/doctor.ts +9 -11
  16. package/src/cli.ts +6 -6
  17. package/src/db/{009-binding-fact-semantics.ts → binding-fact-semantics.ts} +2 -2
  18. package/src/db/{000-call-fact-repository.ts → call-fact-repository.ts} +12 -9
  19. package/src/db/{003-current-fact-semantics.ts → current-fact-semantics.ts} +8 -8
  20. package/src/db/event-fact-semantics.ts +347 -0
  21. package/src/db/event-surface-invalidation.ts +38 -0
  22. package/src/db/{002-fact-json-inventory.ts → fact-json-inventory.ts} +16 -0
  23. package/src/db/{001-fact-lifecycle.ts → fact-lifecycle.ts} +3 -3
  24. package/src/db/migrations.ts +12 -1
  25. package/src/db/{007-package-fact-semantics.ts → package-fact-semantics.ts} +6 -6
  26. package/src/db/{010-package-symbol-surface-semantics.ts → package-symbol-surface-semantics.ts} +2 -2
  27. package/src/db/{004-package-target-invalidation.ts → package-target-invalidation.ts} +80 -1
  28. package/src/db/{008-relative-fact-semantics.ts → relative-fact-semantics.ts} +3 -3
  29. package/src/db/{006-relative-symbol-resolution.ts → relative-symbol-resolution.ts} +1 -1
  30. package/src/db/repositories.ts +31 -5
  31. package/src/db/{005-schema-structure.ts → schema-structure.ts} +41 -1
  32. package/src/db/schema.ts +6 -2
  33. package/src/db/{011-symbol-call-semantics.ts → symbol-call-semantics.ts} +1 -1
  34. package/src/index.ts +3 -3
  35. package/src/indexer/repository-indexer.ts +50 -11
  36. package/src/indexer/workspace-indexer.ts +3 -3
  37. package/src/linker/{007-call-edge-insertion.ts → call-edge-insertion.ts} +2 -2
  38. package/src/linker/{002-call-evidence.ts → call-evidence.ts} +2 -2
  39. package/src/linker/cross-repo-linker.ts +30 -8
  40. package/src/linker/dynamic-edge-resolver.ts +1 -1
  41. package/src/linker/event-environment-link.ts +211 -0
  42. package/src/linker/event-shape-candidate-linker.ts +161 -0
  43. package/src/linker/{004-event-subscription-handler-linker.ts → event-subscription-handler-linker.ts} +124 -20
  44. package/src/linker/{006-event-template-link.ts → event-template-link.ts} +40 -6
  45. package/src/linker/external-http-target.ts +1 -1
  46. package/src/linker/helper-package-linker.ts +1 -1
  47. package/src/linker/{000-implementation-candidates.ts → implementation-candidates.ts} +1 -1
  48. package/src/linker/{001-implementation-evidence-projection.ts → implementation-evidence-projection.ts} +1 -1
  49. package/src/linker/odata-path-normalizer.ts +1 -1
  50. package/src/linker/{005-odata-path-structure.ts → odata-path-structure.ts} +1 -1
  51. package/src/linker/package-event-constant-resolver.ts +298 -0
  52. package/src/linker/{003-package-import-symbol-resolver.ts → package-import-symbol-resolver.ts} +3 -3
  53. package/src/linker/service-resolver.ts +2 -2
  54. package/src/output/{001-compact-json-output.ts → compact-json-output.ts} +1 -1
  55. package/src/output/table-output.ts +13 -1
  56. package/src/parsers/{006-binding-identity.ts → binding-identity.ts} +3 -3
  57. package/src/parsers/{011-binding-lexical-scope.ts → binding-lexical-scope.ts} +1 -1
  58. package/src/parsers/{021-binding-visibility.ts → binding-visibility.ts} +1 -1
  59. package/src/parsers/decorator-parser.ts +9 -53
  60. package/src/parsers/environment-declarations.ts +327 -0
  61. package/src/parsers/event-call-analysis.ts +242 -0
  62. package/src/parsers/event-environment-reference.ts +231 -0
  63. package/src/parsers/event-loop-registration.ts +132 -0
  64. package/src/parsers/event-name-import-resolution.ts +243 -0
  65. package/src/parsers/event-receiver-analysis.ts +404 -0
  66. package/src/parsers/{005-event-subscription-facts.ts → event-subscription-facts.ts} +6 -2
  67. package/src/parsers/generated-constants-parser.ts +80 -14
  68. package/src/parsers/{016-local-symbol-reference.ts → local-symbol-reference.ts} +2 -2
  69. package/src/parsers/operation-path-analysis.ts +1 -1
  70. package/src/parsers/{023-outbound-call-classifier.ts → outbound-call-classifier.ts} +30 -127
  71. package/src/parsers/outbound-call-parser.ts +15 -3
  72. package/src/parsers/{022-outbound-expression-analysis.ts → outbound-expression-analysis.ts} +4 -4
  73. package/src/parsers/{018-package-commonjs-syntax.ts → package-commonjs-syntax.ts} +1 -1
  74. package/src/parsers/{012-package-fact-contract.ts → package-fact-contract.ts} +3 -3
  75. package/src/parsers/{010-package-public-surface-analysis.ts → package-public-surface-analysis.ts} +2 -2
  76. package/src/parsers/{003-package-public-surface.ts → package-public-surface.ts} +3 -3
  77. package/src/parsers/{008-package-surface-publication.ts → package-surface-publication.ts} +1 -1
  78. package/src/parsers/{001-query-entity-resolution.ts → query-entity-resolution.ts} +1 -1
  79. package/src/parsers/{015-service-binding-collector.ts → service-binding-collector.ts} +3 -3
  80. package/src/parsers/{014-service-binding-helper-flow.ts → service-binding-helper-flow.ts} +2 -2
  81. package/src/parsers/service-binding-parser-helpers.ts +1 -1
  82. package/src/parsers/service-binding-parser.ts +2 -2
  83. package/src/parsers/{007-source-fact-reconciliation.ts → source-fact-reconciliation.ts} +3 -3
  84. package/src/parsers/{020-stable-local-value.ts → stable-local-value.ts} +32 -11
  85. package/src/parsers/string-constant-lookups.ts +358 -0
  86. package/src/parsers/{009-symbol-call-facts.ts → symbol-call-facts.ts} +3 -3
  87. package/src/parsers/{017-symbol-derived-contexts.ts → symbol-derived-contexts.ts} +2 -2
  88. package/src/parsers/symbol-parser.ts +6 -6
  89. package/src/trace/{021-compact-decision-normalization.ts → compact-decision-normalization.ts} +2 -2
  90. package/src/trace/{020-compact-field-projection.ts → compact-field-projection.ts} +3 -3
  91. package/src/trace/{024-compact-observation-decision.ts → compact-observation-decision.ts} +3 -3
  92. package/src/trace/{016-compact-projector.ts → compact-projector.ts} +4 -4
  93. package/src/trace/{018-compact-trace.ts → compact-trace.ts} +3 -3
  94. package/src/trace/{006-contextual-projection.ts → contextual-projection.ts} +1 -1
  95. package/src/trace/{008-contextual-runtime-state.ts → contextual-runtime-state.ts} +1 -1
  96. package/src/trace/{001-dynamic-identity.ts → dynamic-identity.ts} +2 -2
  97. package/src/trace/{003-dynamic-references.ts → dynamic-references.ts} +2 -2
  98. package/src/trace/dynamic-targets.ts +6 -6
  99. package/src/trace/{030-event-runtime-resolution.ts → event-runtime-resolution.ts} +26 -5
  100. package/src/trace/event-shape-candidate-trace.ts +172 -0
  101. package/src/trace/{011-event-subscriber-traversal.ts → event-subscriber-traversal.ts} +20 -8
  102. package/src/trace/evidence.ts +9 -2
  103. package/src/trace/implementation-hints.ts +2 -2
  104. package/src/trace/{005-implementation-selection.ts → implementation-selection.ts} +2 -2
  105. package/src/trace/{031-local-call-expansion.ts → local-call-expansion.ts} +3 -3
  106. package/src/trace/{009-selected-handler-provenance.ts → selected-handler-provenance.ts} +1 -1
  107. package/src/trace/selectors.ts +1 -1
  108. package/src/trace/{017-trace-context.ts → trace-context.ts} +2 -2
  109. package/src/trace/{015-trace-edge-recorder.ts → trace-edge-recorder.ts} +2 -2
  110. package/src/trace/{019-trace-edge-semantics.ts → trace-edge-semantics.ts} +3 -3
  111. package/src/trace/trace-engine.ts +10 -10
  112. package/src/trace/{022-trace-fact-preflight.ts → trace-fact-preflight.ts} +2 -2
  113. package/src/trace/{025-trace-implementation-scope.ts → trace-implementation-scope.ts} +1 -1
  114. package/src/trace/{028-trace-operation-execution.ts → trace-operation-execution.ts} +10 -10
  115. package/src/trace/{013-trace-root-scopes.ts → trace-root-scopes.ts} +3 -3
  116. package/src/trace/{027-trace-scope-execution.ts → trace-scope-execution.ts} +35 -43
  117. package/src/trace/{029-trace-start-implementation.ts → trace-start-implementation.ts} +7 -7
  118. package/src/trace/{026-trace-start-scope.ts → trace-start-scope.ts} +5 -5
  119. package/src/trace/{010-traversal-scope.ts → traversal-scope.ts} +1 -1
  120. package/src/types.ts +17 -1
  121. package/src/utils/event-skeleton.ts +207 -0
  122. package/src/version.ts +1 -1
  123. package/dist/chunk-3N3B5KHV.js.map +0 -1
  124. /package/src/cli/{000-clean.ts → clean.ts} +0 -0
  125. /package/src/cli/{003-doctor-package-resolution.ts → doctor-package-resolution.ts} +0 -0
  126. /package/src/cli/{001-index-summary.ts → index-summary.ts} +0 -0
  127. /package/src/db/{014-binding-helper-provenance.ts → binding-helper-provenance.ts} +0 -0
  128. /package/src/db/{012-binding-reference-proof.ts → binding-reference-proof.ts} +0 -0
  129. /package/src/db/{013-index-publication-failure.ts → index-publication-failure.ts} +0 -0
  130. /package/src/output/{000-stdout-policy.ts → stdout-policy.ts} +0 -0
  131. /package/src/parsers/{019-binding-assignment-targets.ts → binding-assignment-targets.ts} +0 -0
  132. /package/src/parsers/{000-direct-query-execution.ts → direct-query-execution.ts} +0 -0
  133. /package/src/parsers/{013-executable-body-eligibility.ts → executable-body-eligibility.ts} +0 -0
  134. /package/src/parsers/{004-fact-identity.ts → fact-identity.ts} +0 -0
  135. /package/src/parsers/{002-symbol-import-bindings.ts → symbol-import-bindings.ts} +0 -0
  136. /package/src/trace/{014-compact-contract.ts → compact-contract.ts} +0 -0
  137. /package/src/trace/{004-dynamic-candidate-sources.ts → dynamic-candidate-sources.ts} +0 -0
  138. /package/src/trace/{000-dynamic-target-types.ts → dynamic-target-types.ts} +0 -0
  139. /package/src/trace/{007-implementation-start-diagnostic.ts → implementation-start-diagnostic.ts} +0 -0
  140. /package/src/trace/{023-nested-event-scopes.ts → nested-event-scopes.ts} +0 -0
  141. /package/src/trace/{002-trace-diagnostics.ts → trace-diagnostics.ts} +0 -0
  142. /package/src/trace/{012-trace-graph-lookups.ts → trace-graph-lookups.ts} +0 -0
  143. /package/src/utils/{000-bounded-projection.ts → bounded-projection.ts} +0 -0
  144. /package/src/utils/{001-placeholders.ts → placeholders.ts} +0 -0
@@ -3,6 +3,14 @@ import {
3
3
  substituteVariables,
4
4
  type RuntimeSubstitution,
5
5
  } from './dynamic-edge-resolver.js';
6
+ import {
7
+ resolveEventEnvironment,
8
+ } from './event-environment-link.js';
9
+ import {
10
+ eventTemplateVariables,
11
+ parseEventSkeletonFact,
12
+ type EventSkeletonFact,
13
+ } from '../utils/event-skeleton.js';
6
14
 
7
15
  export interface LinkedEventTemplate {
8
16
  targetId: string;
@@ -17,8 +25,11 @@ export function linkEventTemplate(
17
25
  template: string,
18
26
  variables: Record<string, string>,
19
27
  parserReason?: string,
28
+ skeleton?: EventSkeletonFact,
20
29
  ): LinkedEventTemplate {
21
- const substitution = substituteVariables(template, variables);
30
+ const substitution = substituteVariables(
31
+ template, eventTemplateVariables(skeleton, variables),
32
+ );
22
33
  const missing = substitution.missing.length > 0;
23
34
  const unsupportedDynamic = substitution.placeholders.length === 0
24
35
  && parserReason !== undefined;
@@ -47,18 +58,41 @@ export function insertEventCallEdge(
47
58
  evidence: Record<string, unknown>,
48
59
  ): { status: string; callType: string } {
49
60
  const callType = String(call.call_type);
61
+ const skeleton = parseEventSkeletonFact(call.event_skeleton_json);
62
+ const environment = resolveEventEnvironment(
63
+ call.event_skeleton_json,
64
+ call.environmentDeclarationsJson,
65
+ variables,
66
+ );
50
67
  const event = linkEventTemplate(
51
- String(call.event_name_expr ?? ''), variables,
68
+ String(call.event_name_expr ?? ''), environment.variables,
52
69
  typeof call.unresolved_reason === 'string'
53
70
  ? call.unresolved_reason : undefined,
71
+ skeleton,
54
72
  );
55
73
  const edgeType = event.isDynamic
56
74
  ? 'DYNAMIC_EDGE_CANDIDATE'
57
75
  : callType === 'async_emit'
58
76
  ? 'HANDLER_EMITS_EVENT' : 'EVENT_CONSUMED_BY_HANDLER';
59
- const eventEvidence = event.substitution.placeholders.length > 0
60
- ? { ...evidence, eventTemplateResolution: event.substitution }
61
- : evidence;
77
+ const eventEvidence = {
78
+ ...evidence,
79
+ dispatchScope: 'workspace_event_name_only',
80
+ dispatchCertainty: environment.status === 'resolved'
81
+ ? 'environment_declaration_exact'
82
+ : event.isDynamic ? 'runtime_variables_required' : 'static_name_only',
83
+ ...(skeleton ? { eventSkeleton: skeleton } : {}),
84
+ ...(event.substitution.placeholders.length > 0
85
+ ? { eventTemplateResolution: event.substitution } : {}),
86
+ ...(environment.status === 'not_applicable' ? {} : {
87
+ eventEnvironmentResolution: {
88
+ status: environment.status,
89
+ reason: environment.reason,
90
+ provenance: environment.provenance,
91
+ },
92
+ }),
93
+ };
94
+ const unresolvedReason = environment.reason
95
+ ?? event.unresolvedReason;
62
96
  db.prepare(`INSERT INTO graph_edges(
63
97
  workspace_id,edge_type,status,from_kind,from_id,to_kind,to_id,
64
98
  confidence,evidence_json,is_dynamic,unresolved_reason,generation
@@ -66,7 +100,7 @@ export function insertEventCallEdge(
66
100
  workspaceId, edgeType, event.status, 'call', String(call.id),
67
101
  event.targetKind, event.targetId, Number(call.confidence ?? 0.2),
68
102
  JSON.stringify(eventEvidence), event.isDynamic ? 1 : 0,
69
- event.unresolvedReason ?? null, generation,
103
+ unresolvedReason ?? null, generation,
70
104
  );
71
105
  return { status: event.status, callType };
72
106
  }
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { projectBounded } from '../utils/000-bounded-projection.js';
2
+ import { projectBounded } from '../utils/bounded-projection.js';
3
3
 
4
4
  export type ExternalTargetKind = 'destination' | 'static_url' | 'url_expression' | 'unknown';
5
5
  export interface ExternalHttpTarget { kind: ExternalTargetKind; toKind: 'external_destination' | 'external_endpoint'; toId: string; label: string; method?: string; dynamic: boolean; expression?: string; candidateLiteralCount?: number; shownCandidateLiteralCount?: number; omittedCandidateLiteralCount?: number; }
@@ -1,5 +1,5 @@
1
1
  import type { Db } from '../db/connection.js';
2
- import { projectBounded } from '../utils/000-bounded-projection.js';
2
+ import { projectBounded } from '../utils/bounded-projection.js';
3
3
 
4
4
  interface RepoDependencyRow {
5
5
  id: number;
@@ -6,7 +6,7 @@ import {
6
6
  boundedImplementationTargetIds,
7
7
  displayImplementationCandidates,
8
8
  selectedHandlerProvenance,
9
- } from './001-implementation-evidence-projection.js';
9
+ } from './implementation-evidence-projection.js';
10
10
 
11
11
  interface ImplementationCandidate extends Record<string, unknown> {
12
12
  methodId: number;
@@ -2,7 +2,7 @@ import {
2
2
  projectBounded,
3
3
  projectBoundedInOrder,
4
4
  type BoundedProjection,
5
- } from '../utils/000-bounded-projection.js';
5
+ } from '../utils/bounded-projection.js';
6
6
 
7
7
  export interface SelectedHandlerSource {
8
8
  methodId: number;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  analyzeODataPathStructure,
3
3
  type ODataPathStructure,
4
- } from './005-odata-path-structure.js';
4
+ } from './odata-path-structure.js';
5
5
 
6
6
  export interface NormalizedODataOperationPath {
7
7
  rawOperationPath: string;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  scanPlaceholderStructure,
3
3
  type PlaceholderSpan,
4
- } from '../utils/001-placeholders.js';
4
+ } from '../utils/placeholders.js';
5
5
 
6
6
  export type ODataPathStructureReason =
7
7
  | 'path_parenthesis_is_unbalanced'
@@ -0,0 +1,298 @@
1
+ import type { Db } from '../db/connection.js';
2
+ import {
3
+ parsePackageImportReference,
4
+ parsePackagePublicSurfaceFact,
5
+ } from '../parsers/package-fact-contract.js';
6
+ import type {
7
+ PackagePublicSurfaceFact,
8
+ } from '../parsers/package-public-surface.js';
9
+ import type {
10
+ SymbolImportReference,
11
+ } from '../parsers/symbol-import-bindings.js';
12
+
13
+ export interface PackageEventConstantLinkSummary {
14
+ resolved: number;
15
+ ambiguous: number;
16
+ unresolved: number;
17
+ }
18
+
19
+ interface EventConstantCall {
20
+ id: number;
21
+ binding: SymbolImportReference;
22
+ evidence: Record<string, unknown>;
23
+ unresolvedReason?: string | null;
24
+ }
25
+
26
+ interface PackageRepository {
27
+ id: number;
28
+ surface: PackagePublicSurfaceFact;
29
+ }
30
+
31
+ export interface PackageEventConstantResolution {
32
+ status: 'resolved' | 'ambiguous' | 'unresolved';
33
+ reason?: string;
34
+ value?: string;
35
+ sourceFile?: string;
36
+ sourceLine?: number;
37
+ targetRepoId?: number;
38
+ modulePath?: string;
39
+ candidateCount: number;
40
+ eligibleCandidateCount: number;
41
+ complete: boolean;
42
+ }
43
+
44
+ function record(value: unknown): Record<string, unknown> | undefined {
45
+ return value && typeof value === 'object' && !Array.isArray(value)
46
+ ? value as Record<string, unknown> : undefined;
47
+ }
48
+
49
+ function jsonRecord(value: unknown): Record<string, unknown> | undefined {
50
+ if (typeof value !== 'string') return undefined;
51
+ try {
52
+ return record(JSON.parse(value) as unknown);
53
+ } catch {
54
+ return undefined;
55
+ }
56
+ }
57
+
58
+ function callRows(db: Db, workspaceId: number): EventConstantCall[] {
59
+ const rows = db.prepare(`SELECT c.id,c.evidence_json evidenceJson,
60
+ c.unresolved_reason unresolvedReason
61
+ FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
62
+ WHERE r.workspace_id=?
63
+ AND c.call_type IN ('async_emit','async_subscribe')
64
+ AND json_extract(c.evidence_json,
65
+ '$.eventNameConstantImportBinding.moduleKind')='package'
66
+ ORDER BY c.id`).all(workspaceId);
67
+ return rows.flatMap((row) => {
68
+ const evidence = jsonRecord(row.evidenceJson);
69
+ const binding = parsePackageImportReference(
70
+ evidence?.eventNameConstantImportBinding,
71
+ );
72
+ return typeof row.id === 'number' && evidence && binding ? [{
73
+ id: row.id, evidence, binding,
74
+ unresolvedReason: typeof row.unresolvedReason === 'string'
75
+ ? row.unresolvedReason : null,
76
+ }] : [];
77
+ });
78
+ }
79
+
80
+ function parseSurface(
81
+ value: unknown,
82
+ packageName: string,
83
+ ): PackagePublicSurfaceFact | undefined {
84
+ if (typeof value !== 'string') return undefined;
85
+ try {
86
+ return parsePackagePublicSurfaceFact(
87
+ JSON.parse(value) as unknown, packageName,
88
+ );
89
+ } catch {
90
+ return undefined;
91
+ }
92
+ }
93
+
94
+ function repositories(
95
+ db: Db,
96
+ workspaceId: number,
97
+ packageName: string,
98
+ ): PackageRepository[] {
99
+ return db.prepare(`SELECT id,package_public_surface_json surfaceJson
100
+ FROM repositories WHERE workspace_id=? AND package_name=?
101
+ ORDER BY id`).all(workspaceId, packageName).flatMap((row) => {
102
+ const surface = parseSurface(row.surfaceJson, packageName);
103
+ return typeof row.id === 'number' && surface
104
+ ? [{ id: row.id, surface }] : [];
105
+ });
106
+ }
107
+
108
+ function failure(
109
+ reason: string,
110
+ fields: Partial<PackageEventConstantResolution> = {},
111
+ ): PackageEventConstantResolution {
112
+ return {
113
+ status: reason === 'event_name_constant_container_ambiguous'
114
+ ? 'ambiguous' : 'unresolved',
115
+ reason,
116
+ candidateCount: 0,
117
+ eligibleCandidateCount: 0,
118
+ complete: true,
119
+ ...fields,
120
+ };
121
+ }
122
+
123
+ function entryModule(
124
+ repository: PackageRepository,
125
+ binding: SymbolImportReference,
126
+ ): string | PackageEventConstantResolution {
127
+ const surface = repository.surface;
128
+ if (surface.status !== 'complete' || surface.omitted > 0) return failure(
129
+ 'event_name_constant_container_ambiguous', { complete: false },
130
+ );
131
+ const matches = surface.entries.filter((entry) =>
132
+ entry.entry === binding.requestedModuleSubpath);
133
+ return matches.length === 1 && matches[0]
134
+ ? matches[0].modulePath
135
+ : failure('event_name_constant_container_ambiguous', {
136
+ complete: matches.length === 0 && surface.omitted === 0,
137
+ });
138
+ }
139
+
140
+ function moduleFile(sourceFile: string): string {
141
+ return sourceFile.replace(/\.(?:d\.)?(?:ts|js)$/, '');
142
+ }
143
+
144
+ function constantRows(
145
+ db: Db,
146
+ repoId: number,
147
+ modulePath: string,
148
+ publicName: string,
149
+ ): Array<Record<string, unknown>> {
150
+ return db.prepare(`SELECT id,source_file sourceFile,
151
+ source_line sourceLine,value,exported,stable,
152
+ resolution_status resolutionStatus,unresolved_reason unresolvedReason
153
+ FROM generated_constants
154
+ WHERE repo_id=? AND name=?
155
+ ORDER BY source_file COLLATE BINARY,declaration_start_offset,id`).all(
156
+ repoId, publicName,
157
+ ).filter((row) =>
158
+ typeof row.sourceFile === 'string'
159
+ && moduleFile(row.sourceFile) === modulePath);
160
+ }
161
+
162
+ function resolveRows(
163
+ rows: Array<Record<string, unknown>>,
164
+ repoId: number,
165
+ modulePath: string,
166
+ ): PackageEventConstantResolution {
167
+ const fields = {
168
+ targetRepoId: repoId,
169
+ modulePath,
170
+ candidateCount: rows.length,
171
+ complete: true,
172
+ };
173
+ if (rows.length !== 1 || !rows[0]) return failure(
174
+ 'event_name_constant_container_ambiguous',
175
+ fields,
176
+ );
177
+ const row = rows[0];
178
+ if (Number(row.exported) !== 1) return failure(
179
+ 'event_name_constant_container_not_exported', fields,
180
+ );
181
+ if (Number(row.stable) !== 1) return failure(
182
+ 'event_name_constant_container_mutable', fields,
183
+ );
184
+ if (row.resolutionStatus !== 'resolved'
185
+ || typeof row.value !== 'string') return failure(
186
+ String(row.unresolvedReason) === 'event_name_constant_container_mutable'
187
+ ? 'event_name_constant_container_mutable'
188
+ : 'event_name_constant_member_not_string',
189
+ fields,
190
+ );
191
+ return {
192
+ ...fields,
193
+ status: 'resolved',
194
+ value: row.value,
195
+ sourceFile: String(row.sourceFile),
196
+ sourceLine: Number(row.sourceLine),
197
+ eligibleCandidateCount: 1,
198
+ };
199
+ }
200
+
201
+ function resolveCall(
202
+ db: Db,
203
+ workspaceId: number,
204
+ call: EventConstantCall,
205
+ ): PackageEventConstantResolution {
206
+ const packageName = call.binding.requestedPackageName ?? '';
207
+ const matches = repositories(db, workspaceId, packageName);
208
+ if (matches.length !== 1 || !matches[0]) return failure(
209
+ 'event_name_constant_container_ambiguous',
210
+ { candidateCount: matches.length, complete: matches.length === 0 },
211
+ );
212
+ const module = entryModule(matches[0], call.binding);
213
+ if (typeof module !== 'string') return {
214
+ ...module, targetRepoId: matches[0].id,
215
+ };
216
+ return resolveRows(
217
+ constantRows(
218
+ db, matches[0].id, module, call.binding.requestedPublicName,
219
+ ),
220
+ matches[0].id,
221
+ module,
222
+ );
223
+ }
224
+
225
+ export function expectedPackageEventConstantResolution(
226
+ db: Db,
227
+ workspaceId: number,
228
+ binding: SymbolImportReference,
229
+ ): PackageEventConstantResolution {
230
+ return resolveCall(db, workspaceId, {
231
+ id: 0,
232
+ binding,
233
+ evidence: {},
234
+ });
235
+ }
236
+
237
+ function receiverReason(call: EventConstantCall): string | null {
238
+ return call.evidence.receiverClassification === 'unproven'
239
+ ? call.unresolvedReason ?? 'event_receiver_unproven_binding' : null;
240
+ }
241
+
242
+ function resolutionEvidence(
243
+ call: EventConstantCall,
244
+ resolution: PackageEventConstantResolution,
245
+ ): string {
246
+ const evidence = { ...call.evidence };
247
+ delete evidence.eventNameUnresolvedReason;
248
+ delete evidence.eventNameStatus;
249
+ delete evidence.eventNameSourceKind;
250
+ delete evidence.eventNamePlaceholderKeys;
251
+ if (resolution.status !== 'resolved')
252
+ evidence.eventNameUnresolvedReason = resolution.reason;
253
+ return JSON.stringify({
254
+ ...evidence,
255
+ ...(resolution.status === 'resolved' ? {
256
+ eventNameConstant: {
257
+ sourceKind: 'package_static_string',
258
+ sourceFile: resolution.sourceFile,
259
+ sourceLine: resolution.sourceLine,
260
+ },
261
+ } : {}),
262
+ eventNamePackageConstantResolution: {
263
+ status: resolution.status,
264
+ reason: resolution.reason,
265
+ candidateCount: resolution.candidateCount,
266
+ eligibleCandidateCount: resolution.eligibleCandidateCount,
267
+ selectedCandidateCount: resolution.status === 'resolved' ? 1 : 0,
268
+ candidateSetComplete: resolution.complete,
269
+ requestedPackageName: call.binding.requestedPackageName,
270
+ requestedModuleSubpath: call.binding.requestedModuleSubpath,
271
+ requestedPublicName: call.binding.requestedPublicName,
272
+ resolvedModulePath: resolution.modulePath,
273
+ targetRepositoryId: resolution.targetRepoId,
274
+ },
275
+ });
276
+ }
277
+
278
+ export function linkPackageEventConstants(
279
+ db: Db,
280
+ workspaceId: number,
281
+ ): PackageEventConstantLinkSummary {
282
+ const summary = { resolved: 0, ambiguous: 0, unresolved: 0 };
283
+ const update = db.prepare(`UPDATE outbound_calls SET event_name_expr=?,
284
+ unresolved_reason=?,evidence_json=? WHERE id=?`);
285
+ for (const call of callRows(db, workspaceId)) {
286
+ const resolution = resolveCall(db, workspaceId, call);
287
+ const reason = receiverReason(call) ?? resolution.reason ?? null;
288
+ const sourceExpression = call.evidence.eventNameConstantSourceExpression;
289
+ update.run(
290
+ resolution.value ?? String(sourceExpression ?? ''),
291
+ reason,
292
+ resolutionEvidence(call, resolution),
293
+ call.id,
294
+ );
295
+ summary[resolution.status] += 1;
296
+ }
297
+ return summary;
298
+ }
@@ -3,14 +3,14 @@ import type {
3
3
  PackagePublicScope,
4
4
  PackagePublicSurfaceFact,
5
5
  PublicSurfaceTarget,
6
- } from '../parsers/003-package-public-surface.js';
6
+ } from '../parsers/package-public-surface.js';
7
7
  import type {
8
8
  SymbolImportReference,
9
- } from '../parsers/002-symbol-import-bindings.js';
9
+ } from '../parsers/symbol-import-bindings.js';
10
10
  import {
11
11
  parsePackageImportReference,
12
12
  parsePackagePublicSurfaceFact,
13
- } from '../parsers/012-package-fact-contract.js';
13
+ } from '../parsers/package-fact-contract.js';
14
14
 
15
15
  export interface PackageSymbolLinkSummary {
16
16
  resolved: number;
@@ -1,6 +1,6 @@
1
1
  import type { Db } from '../db/connection.js';
2
- import { extractPlaceholderKeys } from '../utils/001-placeholders.js';
3
- import { canonicalImplementationEvidence } from './000-implementation-candidates.js';
2
+ import { extractPlaceholderKeys } from '../utils/placeholders.js';
3
+ import { canonicalImplementationEvidence } from './implementation-candidates.js';
4
4
  export interface OperationTarget {
5
5
  operationId: number;
6
6
  repoName: string;
@@ -1,4 +1,4 @@
1
- import type { CompactGraphV1 } from '../trace/014-compact-contract.js';
1
+ import type { CompactGraphV1 } from '../trace/compact-contract.js';
2
2
  import { redactValue } from '../utils/redaction.js';
3
3
 
4
4
  export function renderCompactJson(value: CompactGraphV1): string {
@@ -23,7 +23,7 @@ function location(evidence: Record<string, unknown>): string {
23
23
  export function renderTraceTable(result: TraceResult): string {
24
24
  const lines = ['Step Type From To Evidence'];
25
25
  for (const e of result.edges) {
26
- lines.push(`${String(e.step).padEnd(5)} ${e.type.padEnd(20)} ${e.from.slice(0, 34).padEnd(35)} ${e.to.slice(0, 35).padEnd(36)} ${location(e.evidence)}`);
26
+ lines.push(`${String(e.step).padEnd(5)} ${e.type.padEnd(20)} ${e.from.slice(0, 34).padEnd(35)} ${e.to.slice(0, 35).padEnd(36)} ${evidenceSummary(e.evidence)}`);
27
27
  if (e.unresolvedReason)
28
28
  lines.push(...hintLines(e.evidence).map((hint) => ` ${hint}`));
29
29
  }
@@ -31,6 +31,18 @@ export function renderTraceTable(result: TraceResult): string {
31
31
  return `${lines.join('\n')}\n`;
32
32
  }
33
33
 
34
+ function evidenceSummary(evidence: Record<string, unknown>): string {
35
+ const labels = [
36
+ typeof evidence.dispatchScope === 'string'
37
+ ? `scope=${evidence.dispatchScope}` : undefined,
38
+ typeof evidence.dispatchCertainty === 'string'
39
+ ? `certainty=${evidence.dispatchCertainty}` : undefined,
40
+ ].filter((value): value is string => Boolean(value));
41
+ return labels.length > 0
42
+ ? `${location(evidence)} [${labels.join(',')}]`
43
+ : location(evidence);
44
+ }
45
+
34
46
  function diagnosticLines(diagnostic: Record<string, unknown>): string[] {
35
47
  const first = `${String(diagnostic.severity ?? 'info')} ${String(diagnostic.code ?? 'diagnostic')} ${String(diagnostic.message ?? '')}`;
36
48
  const details = diagnosticDetailLines(diagnostic);
@@ -9,7 +9,7 @@ import type {
9
9
  import {
10
10
  executableSymbolCandidates,
11
11
  selectCallOwner,
12
- } from './004-fact-identity.js';
12
+ } from './fact-identity.js';
13
13
  import {
14
14
  bindingSite,
15
15
  createBindingLexicalIndex,
@@ -17,8 +17,8 @@ import {
17
17
  type BindingLexicalIndex,
18
18
  type BindingLexicalSite,
19
19
  type VisibleBinding,
20
- } from './011-binding-lexical-scope.js';
21
- import { selectVisibleBinding } from './021-binding-visibility.js';
20
+ } from './binding-lexical-scope.js';
21
+ import { selectVisibleBinding } from './binding-visibility.js';
22
22
 
23
23
  const scopeChainCap = 16;
24
24
 
@@ -3,7 +3,7 @@ import type { LexicalScopeFact } from '../types.js';
3
3
  import {
4
4
  bindingAssignmentEntries,
5
5
  type BindingAssignmentEntry,
6
- } from './019-binding-assignment-targets.js';
6
+ } from './binding-assignment-targets.js';
7
7
 
8
8
  export type BindingDeclarationKind =
9
9
  | 'const'
@@ -10,7 +10,7 @@ import {
10
10
  type BindingLexicalSite,
11
11
  type BindingSiteCandidate,
12
12
  type VisibleBinding,
13
- } from './011-binding-lexical-scope.js';
13
+ } from './binding-lexical-scope.js';
14
14
 
15
15
  function reachingSites(
16
16
  index: BindingLexicalIndex,
@@ -14,6 +14,10 @@ import {
14
14
  type RepositorySourceContext,
15
15
  } from './ts-project.js';
16
16
  import { normalizePath } from '../utils/path-utils.js';
17
+ import {
18
+ collectStringConstantLookups,
19
+ type StringConstantLookups,
20
+ } from './string-constant-lookups.js';
17
21
 
18
22
  type DecoratorResolution = HandlerMethodFact['decoratorResolution'];
19
23
  type ResolvedArgumentKind = Exclude<
@@ -21,9 +25,7 @@ type ResolvedArgumentKind = Exclude<
21
25
  'lifecycle_implicit' | 'unresolved'
22
26
  >;
23
27
  interface StringLookups {
24
- identifiers: Map<string, string>;
25
- enumMembers: Map<string, string>;
26
- objectProperties: Map<string, string>;
28
+ strings: StringConstantLookups;
27
29
  capDecoratorNames: Map<string, string>;
28
30
  capDecoratorNamespaces: Set<string>;
29
31
  }
@@ -94,60 +96,14 @@ function unwrapExpression(expression: ts.Expression): ts.Expression {
94
96
  ) current = current.expression;
95
97
  return current;
96
98
  }
97
- function stringValue(expression: ts.Expression | undefined): string | undefined {
98
- if (!expression) return undefined;
99
- const unwrapped = unwrapExpression(expression);
100
- return ts.isStringLiteralLike(unwrapped) ? unwrapped.text : undefined;
101
- }
102
- function propertyName(node: ts.PropertyName): string | undefined {
103
- if (ts.isIdentifier(node) || ts.isStringLiteralLike(node)) return node.text;
104
- return undefined;
105
- }
106
- function collectEnumMembers(
107
- statement: ts.EnumDeclaration,
108
- lookups: StringLookups,
109
- ): void {
110
- for (const member of statement.members) {
111
- const name = propertyName(member.name);
112
- const value = stringValue(member.initializer);
113
- if (name && value !== undefined)
114
- lookups.enumMembers.set(`${statement.name.text}.${name}`, value);
115
- }
116
- }
117
- function collectObjectProperties(
118
- name: string,
119
- initializer: ts.Expression,
120
- lookups: StringLookups,
121
- ): void {
122
- const object = unwrapExpression(initializer);
123
- if (!ts.isObjectLiteralExpression(object)) return;
124
- for (const property of object.properties) {
125
- if (!ts.isPropertyAssignment(property)) continue;
126
- const key = propertyName(property.name);
127
- const value = stringValue(property.initializer);
128
- if (key && value !== undefined)
129
- lookups.objectProperties.set(`${name}.${key}`, value);
130
- }
131
- }
132
99
  function collectStringLookups(source: ts.SourceFile): StringLookups {
133
100
  const lookups: StringLookups = {
134
- identifiers: new Map(),
135
- enumMembers: new Map(),
136
- objectProperties: new Map(),
101
+ strings: collectStringConstantLookups(source),
137
102
  capDecoratorNames: new Map(),
138
103
  capDecoratorNamespaces: new Set(),
139
104
  };
140
105
  for (const statement of source.statements) {
141
106
  collectCapDecoratorImports(statement, lookups);
142
- if (ts.isEnumDeclaration(statement)) collectEnumMembers(statement, lookups);
143
- if (!ts.isVariableStatement(statement)
144
- || !(statement.declarationList.flags & ts.NodeFlags.Const)) continue;
145
- for (const declaration of statement.declarationList.declarations) {
146
- if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue;
147
- const value = stringValue(declaration.initializer);
148
- if (value !== undefined) lookups.identifiers.set(declaration.name.text, value);
149
- collectObjectProperties(declaration.name.text, declaration.initializer, lookups);
150
- }
151
107
  }
152
108
  return lookups;
153
109
  }
@@ -226,7 +182,7 @@ function resolveDecoratorArgument(
226
182
  if (ts.isStringLiteralLike(expression))
227
183
  return resolved(rawExpression, argumentExpression, expression.text, 'literal');
228
184
  if (ts.isIdentifier(expression)) {
229
- const value = lookups.identifiers.get(expression.text);
185
+ const value = lookups.strings.identifiers.get(expression.text)?.value;
230
186
  return value === undefined
231
187
  ? unresolved(rawExpression, 'identifier_not_resolved_to_local_const_string', argumentExpression)
232
188
  : resolved(rawExpression, argumentExpression, value, 'const_identifier');
@@ -234,10 +190,10 @@ function resolveDecoratorArgument(
234
190
  if (ts.isPropertyAccessExpression(expression)
235
191
  && ts.isIdentifier(expression.expression)) {
236
192
  const key = `${expression.expression.text}.${expression.name.text}`;
237
- const enumValue = lookups.enumMembers.get(key);
193
+ const enumValue = lookups.strings.enumMembers.get(key)?.value;
238
194
  if (enumValue !== undefined)
239
195
  return resolved(rawExpression, argumentExpression, enumValue, 'enum_member');
240
- const objectValue = lookups.objectProperties.get(key);
196
+ const objectValue = lookups.strings.objectProperties.get(key)?.value;
241
197
  if (objectValue !== undefined)
242
198
  return resolved(rawExpression, argumentExpression, objectValue, 'const_object_property');
243
199
  }