@saptools/service-flow 0.1.67 → 0.1.69

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 (101) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +27 -9
  3. package/TECHNICAL-NOTE.md +36 -0
  4. package/dist/chunk-3N3B5KHV.js +19596 -0
  5. package/dist/chunk-3N3B5KHV.js.map +1 -0
  6. package/dist/cli.js +2645 -521
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +67 -2
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/001-index-summary.ts +22 -0
  12. package/src/cli/003-doctor-package-resolution.ts +68 -0
  13. package/src/cli/doctor.ts +25 -14
  14. package/src/cli.ts +151 -87
  15. package/src/db/000-call-fact-repository.ts +499 -340
  16. package/src/db/001-fact-lifecycle.ts +60 -30
  17. package/src/db/002-fact-json-inventory.ts +169 -0
  18. package/src/db/003-current-fact-semantics.ts +699 -0
  19. package/src/db/004-package-target-invalidation.ts +183 -0
  20. package/src/db/005-schema-structure.ts +201 -0
  21. package/src/db/006-relative-symbol-resolution.ts +464 -0
  22. package/src/db/007-package-fact-semantics.ts +573 -0
  23. package/src/db/008-relative-fact-semantics.ts +210 -0
  24. package/src/db/009-binding-fact-semantics.ts +352 -0
  25. package/src/db/010-package-symbol-surface-semantics.ts +320 -0
  26. package/src/db/011-symbol-call-semantics.ts +144 -0
  27. package/src/db/012-binding-reference-proof.ts +268 -0
  28. package/src/db/013-index-publication-failure.ts +91 -0
  29. package/src/db/014-binding-helper-provenance.ts +17 -0
  30. package/src/db/migrations.ts +16 -3
  31. package/src/db/repositories.ts +130 -6
  32. package/src/db/schema.ts +4 -2
  33. package/src/index.ts +12 -0
  34. package/src/indexer/cds-extension-resolver.ts +27 -3
  35. package/src/indexer/repository-indexer.ts +135 -13
  36. package/src/indexer/workspace-indexer.ts +237 -34
  37. package/src/linker/003-package-import-symbol-resolver.ts +363 -131
  38. package/src/linker/004-event-subscription-handler-linker.ts +34 -11
  39. package/src/linker/005-odata-path-structure.ts +371 -0
  40. package/src/linker/006-event-template-link.ts +72 -0
  41. package/src/linker/007-call-edge-insertion.ts +568 -0
  42. package/src/linker/cross-repo-linker.ts +4 -166
  43. package/src/linker/odata-path-normalizer.ts +273 -180
  44. package/src/linker/service-resolver.ts +197 -77
  45. package/src/parsers/000-direct-query-execution.ts +11 -0
  46. package/src/parsers/002-symbol-import-bindings.ts +516 -0
  47. package/src/parsers/003-package-public-surface.ts +661 -0
  48. package/src/parsers/004-fact-identity.ts +108 -0
  49. package/src/parsers/005-event-subscription-facts.ts +281 -0
  50. package/src/parsers/006-binding-identity.ts +348 -0
  51. package/src/parsers/007-source-fact-reconciliation.ts +105 -0
  52. package/src/parsers/008-package-surface-publication.ts +82 -0
  53. package/src/parsers/009-symbol-call-facts.ts +528 -0
  54. package/src/parsers/010-package-public-surface-analysis.ts +352 -0
  55. package/src/parsers/011-binding-lexical-scope.ts +583 -0
  56. package/src/parsers/012-package-fact-contract.ts +306 -0
  57. package/src/parsers/013-executable-body-eligibility.ts +35 -0
  58. package/src/parsers/014-service-binding-helper-flow.ts +306 -0
  59. package/src/parsers/015-service-binding-collector.ts +693 -0
  60. package/src/parsers/016-local-symbol-reference.ts +261 -0
  61. package/src/parsers/017-symbol-derived-contexts.ts +268 -0
  62. package/src/parsers/018-package-commonjs-syntax.ts +142 -0
  63. package/src/parsers/019-binding-assignment-targets.ts +76 -0
  64. package/src/parsers/020-stable-local-value.ts +217 -0
  65. package/src/parsers/021-binding-visibility.ts +168 -0
  66. package/src/parsers/022-outbound-expression-analysis.ts +700 -0
  67. package/src/parsers/023-outbound-call-classifier.ts +692 -0
  68. package/src/parsers/operation-path-analysis.ts +6 -1
  69. package/src/parsers/outbound-call-parser.ts +162 -512
  70. package/src/parsers/package-json-parser.ts +45 -3
  71. package/src/parsers/service-binding-parser-helpers.ts +86 -15
  72. package/src/parsers/service-binding-parser.ts +147 -597
  73. package/src/parsers/symbol-parser.ts +513 -352
  74. package/src/trace/002-trace-diagnostics.ts +36 -1
  75. package/src/trace/007-implementation-start-diagnostic.ts +1 -0
  76. package/src/trace/011-event-subscriber-traversal.ts +100 -8
  77. package/src/trace/013-trace-root-scopes.ts +2 -3
  78. package/src/trace/014-compact-contract.ts +6 -0
  79. package/src/trace/015-trace-edge-recorder.ts +61 -4
  80. package/src/trace/016-compact-projector.ts +15 -17
  81. package/src/trace/019-trace-edge-semantics.ts +6 -10
  82. package/src/trace/020-compact-field-projection.ts +122 -38
  83. package/src/trace/021-compact-decision-normalization.ts +171 -0
  84. package/src/trace/022-trace-fact-preflight.ts +21 -0
  85. package/src/trace/023-nested-event-scopes.ts +23 -0
  86. package/src/trace/024-compact-observation-decision.ts +81 -0
  87. package/src/trace/025-trace-implementation-scope.ts +123 -0
  88. package/src/trace/026-trace-start-scope.ts +336 -0
  89. package/src/trace/027-trace-scope-execution.ts +566 -0
  90. package/src/trace/028-trace-operation-execution.ts +336 -0
  91. package/src/trace/029-trace-start-implementation.ts +172 -0
  92. package/src/trace/030-event-runtime-resolution.ts +151 -0
  93. package/src/trace/031-local-call-expansion.ts +37 -0
  94. package/src/trace/implementation-hints.ts +9 -6
  95. package/src/trace/selectors.ts +1 -0
  96. package/src/trace/trace-engine.ts +122 -624
  97. package/src/types.ts +57 -0
  98. package/src/utils/001-placeholders.ts +188 -10
  99. package/src/version.ts +1 -1
  100. package/dist/chunk-ZQABU7MR.js +0 -12151
  101. package/dist/chunk-ZQABU7MR.js.map +0 -1
@@ -0,0 +1,699 @@
1
+ import type { Db } from './connection.js';
2
+ import type { CallType, RepoKind } from '../types.js';
3
+ import { ANALYZER_VERSION } from '../version.js';
4
+ import {
5
+ invalidPackageFactCategories,
6
+ } from './007-package-fact-semantics.js';
7
+ import {
8
+ invalidRelativeFactCategories,
9
+ } from './008-relative-fact-semantics.js';
10
+ import {
11
+ invalidBindingFactCategories,
12
+ } from './009-binding-fact-semantics.js';
13
+ import {
14
+ invalidSymbolFactCategories,
15
+ } from './011-symbol-call-semantics.js';
16
+
17
+ export type PackageFactPhase = 'pre_package' | 'terminal';
18
+
19
+ export interface FactSemanticCategoryCount {
20
+ category: string;
21
+ count: number;
22
+ }
23
+
24
+ const executableKindSql =
25
+ "'event_registration','callback','method','object_method','function'";
26
+ const callTypeAllowlist: Record<CallType, true> = {
27
+ remote_action: true,
28
+ remote_query: true,
29
+ remote_entity_read: true,
30
+ remote_entity_mutation: true,
31
+ remote_entity_delete: true,
32
+ remote_entity_media: true,
33
+ remote_entity_candidate: true,
34
+ local_db_query: true,
35
+ external_http: true,
36
+ async_emit: true,
37
+ async_subscribe: true,
38
+ local_service_call: true,
39
+ unknown: true,
40
+ };
41
+ const repositoryKindAllowlist: Record<RepoKind, true> = {
42
+ 'cap-service': true,
43
+ 'cap-db-model': true,
44
+ 'helper-package': true,
45
+ mixed: true,
46
+ unknown: true,
47
+ };
48
+ const symbolStrategies = [
49
+ 'same_file_exact',
50
+ 'exported_exact',
51
+ 'exact_symbol_match',
52
+ 'relative_import_class_instance_method',
53
+ 'relative_import_namespace_member',
54
+ 'relative_import_static_accessor_instance_method',
55
+ 'relative_import_path_disambiguated',
56
+ 'relative_import_exported_exact',
57
+ 'proxy_member_exported_object_map',
58
+ 'proxy_member_no_global_name_fallback',
59
+ 'package_import_pending',
60
+ 'package_import_derived_member_unsupported',
61
+ 'package_public_surface_exact',
62
+ 'package_public_surface_ambiguous',
63
+ 'package_public_surface_unresolved',
64
+ ] as const;
65
+ const symbolReasons = [
66
+ 'multiple_same_file_symbol_targets',
67
+ 'multiple_exported_symbol_targets',
68
+ 'no_local_symbol_target',
69
+ 'symbol_target_has_no_executable_body',
70
+ 'relative_import_provenance_missing',
71
+ 'relative_import_type_only',
72
+ 'relative_import_module_resolution_ambiguous',
73
+ 'relative_import_requested_module_has_no_executable_body',
74
+ 'relative_import_proxy_alias_targets_package_unsupported',
75
+ 'relative_import_requested_module_has_no_target',
76
+ 'multiple_relative_class_targets_in_requested_module',
77
+ 'multiple_namespace_targets_in_requested_module',
78
+ 'multiple_accessor_targets_in_requested_module',
79
+ 'multiple_exported_targets_in_requested_module',
80
+ 'multiple_proxy_targets_in_requested_module',
81
+ 'package_resolution_pending',
82
+ 'package_derived_member_provenance_insufficient',
83
+ 'package_repository_scope_ambiguous',
84
+ 'package_repository_not_indexed',
85
+ 'package_public_surface_unsupported',
86
+ 'public_surface_evidence_incomplete',
87
+ 'package_binding_type_only',
88
+ 'package_public_scope_duplicate',
89
+ 'package_public_name_not_exposed',
90
+ 'package_public_target_ambiguous',
91
+ 'public_symbol_has_no_executable_body',
92
+ ] as const;
93
+ const incompleteReasons = [
94
+ 'relative_import_provenance_missing',
95
+ 'relative_import_module_resolution_ambiguous',
96
+ 'package_resolution_pending',
97
+ 'package_repository_scope_ambiguous',
98
+ 'package_public_surface_unsupported',
99
+ 'public_surface_evidence_incomplete',
100
+ ] as const;
101
+ const packageStrategies = [
102
+ 'package_import_pending',
103
+ 'package_public_surface_exact',
104
+ 'package_public_surface_ambiguous',
105
+ 'package_public_surface_unresolved',
106
+ ] as const;
107
+ const relativeStrategies = [
108
+ 'relative_import_class_instance_method',
109
+ 'relative_import_namespace_member',
110
+ 'relative_import_static_accessor_instance_method',
111
+ 'relative_import_path_disambiguated',
112
+ 'relative_import_exported_exact',
113
+ 'proxy_member_exported_object_map',
114
+ 'proxy_member_no_global_name_fallback',
115
+ ] as const;
116
+ const simpleStrategies = [
117
+ 'same_file_exact',
118
+ 'exported_exact',
119
+ 'exact_symbol_match',
120
+ ] as const;
121
+
122
+ function sqlTextList(values: readonly string[]): string {
123
+ return values.map((value) => `'${value}'`).join(',');
124
+ }
125
+
126
+ function count(
127
+ db: Db,
128
+ sql: string,
129
+ workspaceId?: number,
130
+ predicateCount = 1,
131
+ ): number {
132
+ const params = Array.from({ length: predicateCount }, () =>
133
+ [ANALYZER_VERSION, workspaceId, workspaceId]).flat();
134
+ const row = db.prepare(sql).get(...params);
135
+ return Number(row?.count ?? 0);
136
+ }
137
+
138
+ function category(
139
+ categoryName: string,
140
+ categoryCount: number,
141
+ ): FactSemanticCategoryCount[] {
142
+ return categoryCount > 0
143
+ ? [{ category: categoryName, count: categoryCount }]
144
+ : [];
145
+ }
146
+
147
+ function currentRepositoryPredicate(alias = 'r'): string {
148
+ return `${alias}.fact_analyzer_version=?
149
+ AND (? IS NULL OR ${alias}.workspace_id=?)`;
150
+ }
151
+
152
+ function repositoryCategories(
153
+ db: Db,
154
+ workspaceId?: number,
155
+ ): FactSemanticCategoryCount[] {
156
+ const invalid = count(db, `SELECT COUNT(*) count FROM repositories r
157
+ WHERE ${currentRepositoryPredicate()}
158
+ AND (typeof(r.kind)<>'text' OR r.kind NOT IN (
159
+ ${sqlTextList(Object.keys(repositoryKindAllowlist))}
160
+ ))`, workspaceId);
161
+ return category('repository_kind_invalid', invalid);
162
+ }
163
+
164
+ function operationCategories(
165
+ db: Db,
166
+ workspaceId?: number,
167
+ ): FactSemanticCategoryCount[] {
168
+ const invalid = count(db, `SELECT COUNT(*) count
169
+ FROM cds_operations fact
170
+ JOIN cds_services service ON service.id=fact.service_id
171
+ JOIN repositories r ON r.id=service.repo_id
172
+ LEFT JOIN cds_operations base ON base.id=fact.base_operation_id
173
+ LEFT JOIN cds_services base_service ON base_service.id=base.service_id
174
+ LEFT JOIN repositories base_repo ON base_repo.id=base_service.repo_id
175
+ WHERE ${currentRepositoryPredicate()}
176
+ AND (typeof(fact.operation_type)<>'text'
177
+ OR fact.operation_type NOT IN ('action','function','event')
178
+ OR typeof(fact.operation_name)<>'text'
179
+ OR length(fact.operation_name)=0
180
+ OR typeof(fact.operation_path)<>'text'
181
+ OR length(fact.operation_path)=0
182
+ OR typeof(fact.source_file)<>'text'
183
+ OR length(fact.source_file)=0
184
+ OR typeof(fact.source_line)<>'integer' OR fact.source_line<1
185
+ OR fact.provenance NOT IN ('direct','inherited')
186
+ OR (fact.provenance='direct' AND fact.base_operation_id IS NOT NULL)
187
+ OR (fact.provenance='inherited' AND (
188
+ base.id IS NULL OR COALESCE(service.is_extend,0)<>1
189
+ OR COALESCE(service.extension_base_status,'')<>'resolved'
190
+ OR service.extension_base_service_id IS NULL
191
+ OR service.extension_base_service_id<>base.service_id
192
+ OR COALESCE(base.provenance,'')<>'direct'
193
+ OR base.base_operation_id IS NOT NULL
194
+ OR base_repo.workspace_id IS NULL
195
+ OR base_repo.workspace_id<>r.workspace_id
196
+ OR fact.operation_type<>base.operation_type
197
+ OR fact.operation_name<>base.operation_name
198
+ OR fact.operation_path<>base.operation_path
199
+ OR fact.params_json<>base.params_json
200
+ OR fact.return_type IS NOT base.return_type
201
+ OR fact.source_file<>base.source_file
202
+ OR fact.source_line<>base.source_line)))`, workspaceId);
203
+ return category('cds_operation_semantics_invalid', invalid);
204
+ }
205
+
206
+ function callSpanCategories(
207
+ db: Db,
208
+ workspaceId?: number,
209
+ ): FactSemanticCategoryCount[] {
210
+ const outbound = count(db, `SELECT COUNT(*) count
211
+ FROM outbound_calls fact JOIN repositories r ON r.id=fact.repo_id
212
+ WHERE ${currentRepositoryPredicate()}
213
+ AND (typeof(fact.source_file)<>'text'
214
+ OR length(fact.source_file)=0
215
+ OR typeof(fact.call_site_start_offset)<>'integer'
216
+ OR typeof(fact.call_site_end_offset)<>'integer'
217
+ OR fact.call_site_start_offset<0
218
+ OR fact.call_site_end_offset<=fact.call_site_start_offset)`, workspaceId);
219
+ const callType = count(db, `SELECT COUNT(*) count
220
+ FROM outbound_calls fact JOIN repositories r ON r.id=fact.repo_id
221
+ WHERE ${currentRepositoryPredicate()}
222
+ AND (typeof(fact.call_type)<>'text'
223
+ OR fact.call_type NOT IN (
224
+ ${sqlTextList(Object.keys(callTypeAllowlist))}
225
+ ))`, workspaceId);
226
+ const symbol = count(db, `SELECT COUNT(*) count
227
+ FROM symbol_calls fact JOIN repositories r ON r.id=fact.repo_id
228
+ WHERE ${currentRepositoryPredicate()}
229
+ AND (typeof(fact.source_file)<>'text'
230
+ OR length(fact.source_file)=0
231
+ OR typeof(fact.callee_expression)<>'text'
232
+ OR length(fact.callee_expression)=0
233
+ OR typeof(fact.call_site_start_offset)<>'integer'
234
+ OR typeof(fact.call_site_end_offset)<>'integer'
235
+ OR fact.call_site_start_offset<0
236
+ OR fact.call_site_end_offset<=fact.call_site_start_offset
237
+ OR fact.call_role NOT IN ('ordinary_call','event_subscribe_handler'))`, workspaceId);
238
+ return [
239
+ ...category('outbound_call_span_invalid', outbound),
240
+ ...category('outbound_call_type_invalid', callType),
241
+ ...category('symbol_call_span_or_role_invalid', symbol),
242
+ ];
243
+ }
244
+
245
+ function duplicateSymbolCallCategories(
246
+ db: Db,
247
+ workspaceId?: number,
248
+ ): FactSemanticCategoryCount[] {
249
+ const duplicate = count(db, `SELECT COUNT(*) count FROM (
250
+ SELECT fact.repo_id,fact.source_file,fact.call_site_start_offset,
251
+ fact.call_site_end_offset,fact.call_role,COUNT(*) duplicate_count
252
+ FROM symbol_calls fact JOIN repositories r ON r.id=fact.repo_id
253
+ WHERE ${currentRepositoryPredicate()}
254
+ GROUP BY fact.repo_id,fact.source_file,fact.call_site_start_offset,
255
+ fact.call_site_end_offset,fact.call_role
256
+ HAVING COUNT(*)<>1
257
+ )`, workspaceId);
258
+ return category('symbol_call_site_duplicate', duplicate);
259
+ }
260
+
261
+ function eventNameCategories(
262
+ db: Db,
263
+ workspaceId?: number,
264
+ ): FactSemanticCategoryCount[] {
265
+ const invalid = count(db, `SELECT COUNT(*) count
266
+ FROM outbound_calls fact JOIN repositories r ON r.id=fact.repo_id
267
+ WHERE ${currentRepositoryPredicate()}
268
+ AND fact.call_type IN ('async_emit','async_subscribe')
269
+ AND (typeof(fact.event_name_expr)<>'text'
270
+ OR length(fact.event_name_expr)=0)`, workspaceId);
271
+ const duplicate = count(db, `SELECT COUNT(*) count FROM (
272
+ SELECT fact.repo_id,fact.source_file,fact.call_site_start_offset,
273
+ fact.call_site_end_offset,COUNT(*) duplicate_count
274
+ FROM outbound_calls fact JOIN repositories r ON r.id=fact.repo_id
275
+ WHERE ${currentRepositoryPredicate()}
276
+ AND fact.call_type='async_subscribe'
277
+ GROUP BY fact.repo_id,fact.source_file,fact.call_site_start_offset,
278
+ fact.call_site_end_offset HAVING COUNT(*)<>1
279
+ )`, workspaceId);
280
+ return [
281
+ ...category('event_name_invalid', invalid),
282
+ ...category('async_subscription_site_duplicate', duplicate),
283
+ ];
284
+ }
285
+
286
+ function outboundOwnerCount(db: Db, workspaceId?: number): number {
287
+ return count(db, `WITH eligible AS (
288
+ SELECT fact.id fact_id,s.id symbol_id,
289
+ DENSE_RANK() OVER (PARTITION BY fact.id ORDER BY
290
+ CASE WHEN fact.call_type='async_subscribe'
291
+ AND s.kind='event_registration'
292
+ AND s.start_offset=fact.call_site_start_offset
293
+ AND s.end_offset=fact.call_site_end_offset THEN 0 ELSE 1 END,
294
+ s.end_offset-s.start_offset,
295
+ CASE s.kind WHEN 'event_registration' THEN 0 WHEN 'callback' THEN 1
296
+ WHEN 'method' THEN 2 WHEN 'object_method' THEN 3 ELSE 4 END,
297
+ s.start_offset,s.end_offset,s.qualified_name COLLATE BINARY) owner_rank
298
+ FROM outbound_calls fact JOIN repositories r ON r.id=fact.repo_id
299
+ JOIN symbols s ON s.repo_id=fact.repo_id
300
+ AND s.source_file=fact.source_file
301
+ AND s.kind IN (${executableKindSql})
302
+ AND s.start_offset<=fact.call_site_start_offset
303
+ AND s.end_offset>=fact.call_site_end_offset
304
+ AND (fact.call_type<>'async_subscribe'
305
+ OR (s.kind='event_registration'
306
+ AND s.start_offset=fact.call_site_start_offset
307
+ AND s.end_offset=fact.call_site_end_offset))
308
+ WHERE ${currentRepositoryPredicate()}
309
+ ), best AS (
310
+ SELECT fact_id,COUNT(*) best_count,MAX(symbol_id) symbol_id
311
+ FROM eligible WHERE owner_rank=1 GROUP BY fact_id
312
+ )
313
+ SELECT COUNT(*) count FROM outbound_calls fact
314
+ JOIN repositories r ON r.id=fact.repo_id
315
+ LEFT JOIN best ON best.fact_id=fact.id
316
+ WHERE ${currentRepositoryPredicate()}
317
+ AND ((best.fact_id IS NULL AND (
318
+ fact.source_symbol_id IS NOT NULL
319
+ OR COALESCE(json_extract(fact.evidence_json,
320
+ '$.sourceOwnerResolution'),'')
321
+ <>'ownerless_file_scope'))
322
+ OR (best.fact_id IS NOT NULL AND (
323
+ best.best_count<>1 OR fact.source_symbol_id IS NOT best.symbol_id
324
+ OR COALESCE(json_extract(fact.evidence_json,
325
+ '$.sourceOwnerResolution'),'')<>'owned_exact')))`, workspaceId, 2);
326
+ }
327
+
328
+ function symbolOwnerCount(db: Db, workspaceId?: number): number {
329
+ return count(db, `WITH eligible AS (
330
+ SELECT fact.id fact_id,s.id symbol_id,
331
+ DENSE_RANK() OVER (PARTITION BY fact.id ORDER BY
332
+ CASE WHEN fact.call_role='event_subscribe_handler'
333
+ AND s.kind='event_registration'
334
+ AND s.start_offset=fact.call_site_start_offset
335
+ AND s.end_offset=fact.call_site_end_offset THEN 0 ELSE 1 END,
336
+ s.end_offset-s.start_offset,
337
+ CASE s.kind WHEN 'event_registration' THEN 0 WHEN 'callback' THEN 1
338
+ WHEN 'method' THEN 2 WHEN 'object_method' THEN 3 ELSE 4 END,
339
+ s.start_offset,s.end_offset,s.qualified_name COLLATE BINARY) owner_rank
340
+ FROM symbol_calls fact JOIN repositories r ON r.id=fact.repo_id
341
+ JOIN symbols s ON s.repo_id=fact.repo_id
342
+ AND s.source_file=fact.source_file
343
+ AND s.kind IN (${executableKindSql})
344
+ AND s.start_offset<=fact.call_site_start_offset
345
+ AND s.end_offset>=fact.call_site_end_offset
346
+ AND (fact.call_role<>'event_subscribe_handler'
347
+ OR (s.kind='event_registration'
348
+ AND s.start_offset=fact.call_site_start_offset
349
+ AND s.end_offset=fact.call_site_end_offset))
350
+ WHERE ${currentRepositoryPredicate()}
351
+ ), best AS (
352
+ SELECT fact_id,COUNT(*) best_count,MAX(symbol_id) symbol_id
353
+ FROM eligible WHERE owner_rank=1 GROUP BY fact_id
354
+ )
355
+ SELECT COUNT(*) count FROM symbol_calls fact
356
+ JOIN repositories r ON r.id=fact.repo_id
357
+ LEFT JOIN best ON best.fact_id=fact.id
358
+ LEFT JOIN symbols selected ON selected.id=best.symbol_id
359
+ WHERE ${currentRepositoryPredicate()}
360
+ AND (best.fact_id IS NULL OR best.best_count<>1
361
+ OR fact.caller_symbol_id IS NOT best.symbol_id
362
+ OR typeof(json_extract(fact.evidence_json,'$.caller'))<>'text'
363
+ OR json_extract(fact.evidence_json,'$.caller') COLLATE BINARY
364
+ <>selected.qualified_name COLLATE BINARY)`, workspaceId, 2);
365
+ }
366
+
367
+ function ownerCategories(
368
+ db: Db,
369
+ workspaceId?: number,
370
+ ): FactSemanticCategoryCount[] {
371
+ return [
372
+ ...category('outbound_call_owner_invalid',
373
+ outboundOwnerCount(db, workspaceId)),
374
+ ...category('symbol_call_owner_invalid',
375
+ symbolOwnerCount(db, workspaceId)),
376
+ ];
377
+ }
378
+
379
+ function bindingSiteCount(db: Db, workspaceId?: number): number {
380
+ return count(db, `SELECT COUNT(*) count
381
+ FROM service_bindings fact JOIN repositories r ON r.id=fact.repo_id
382
+ LEFT JOIN symbols owner ON owner.id=fact.symbol_id
383
+ WHERE ${currentRepositoryPredicate()}
384
+ AND (typeof(fact.binding_site_start_offset)<>'integer'
385
+ OR typeof(fact.binding_site_end_offset)<>'integer'
386
+ OR typeof(fact.source_file)<>'text'
387
+ OR length(fact.source_file)=0
388
+ OR typeof(fact.variable_name)<>'text'
389
+ OR length(fact.variable_name)=0
390
+ OR fact.binding_site_start_offset<0
391
+ OR fact.binding_site_end_offset<=fact.binding_site_start_offset
392
+ OR fact.owner_resolution NOT IN
393
+ ('owned_exact','ownerless_file_scope')
394
+ OR (fact.owner_resolution='owned_exact' AND (
395
+ owner.id IS NULL OR owner.repo_id<>fact.repo_id
396
+ OR owner.source_file<>fact.source_file
397
+ OR owner.kind NOT IN (${executableKindSql})
398
+ OR owner.start_offset>fact.binding_site_start_offset
399
+ OR owner.end_offset<fact.binding_site_end_offset))
400
+ OR (fact.owner_resolution='ownerless_file_scope'
401
+ AND fact.symbol_id IS NOT NULL))`, workspaceId);
402
+ }
403
+
404
+ function duplicateBindingSiteCount(db: Db, workspaceId?: number): number {
405
+ return count(db, `SELECT COUNT(*) count FROM (
406
+ SELECT fact.repo_id,fact.source_file,fact.variable_name,
407
+ fact.binding_site_start_offset,fact.binding_site_end_offset,COUNT(*) total
408
+ FROM service_bindings fact JOIN repositories r ON r.id=fact.repo_id
409
+ WHERE ${currentRepositoryPredicate()}
410
+ GROUP BY fact.repo_id,fact.source_file,fact.variable_name,
411
+ fact.binding_site_start_offset,fact.binding_site_end_offset
412
+ HAVING COUNT(*)<>1
413
+ )`, workspaceId);
414
+ }
415
+
416
+ function bindingCategories(
417
+ db: Db,
418
+ workspaceId?: number,
419
+ ): FactSemanticCategoryCount[] {
420
+ return [
421
+ ...category('service_binding_site_or_owner_invalid',
422
+ bindingSiteCount(db, workspaceId)),
423
+ ...category('service_binding_exact_site_duplicate',
424
+ duplicateBindingSiteCount(db, workspaceId)),
425
+ ...category('outbound_binding_reference_invalid',
426
+ bindingReferenceCount(db, workspaceId)),
427
+ ];
428
+ }
429
+
430
+ function bindingReferenceCount(db: Db, workspaceId?: number): number {
431
+ return count(db, `SELECT COUNT(*) count
432
+ FROM outbound_calls fact JOIN repositories r ON r.id=fact.repo_id
433
+ LEFT JOIN service_bindings binding ON binding.id=fact.service_binding_id
434
+ WHERE ${currentRepositoryPredicate()}
435
+ AND (
436
+ COALESCE(json_extract(fact.evidence_json,
437
+ '$.serviceBindingReference.status'),'')
438
+ NOT IN ('resolved_exact','ambiguous','unresolved','not_applicable')
439
+ OR (json_extract(fact.evidence_json,
440
+ '$.serviceBindingReference.status')='resolved_exact' AND (
441
+ binding.id IS NULL OR binding.repo_id<>fact.repo_id
442
+ OR binding.source_file IS NOT json_extract(fact.evidence_json,
443
+ '$.serviceBindingReference.bindingSourceFile')
444
+ OR binding.variable_name IS NOT json_extract(fact.evidence_json,
445
+ '$.serviceBindingReference.variableName')
446
+ OR binding.binding_site_start_offset IS NOT json_extract(
447
+ fact.evidence_json,
448
+ '$.serviceBindingReference.bindingSiteStartOffset')
449
+ OR binding.binding_site_end_offset IS NOT json_extract(
450
+ fact.evidence_json,
451
+ '$.serviceBindingReference.bindingSiteEndOffset')))
452
+ OR (json_extract(fact.evidence_json,
453
+ '$.serviceBindingReference.status') IN ('ambiguous','unresolved')
454
+ AND (fact.service_binding_id IS NOT NULL
455
+ OR typeof(json_extract(fact.evidence_json,
456
+ '$.serviceBindingReference.reason'))<>'text'))
457
+ OR (json_extract(fact.evidence_json,
458
+ '$.serviceBindingReference.status')='not_applicable'
459
+ AND (fact.service_binding_id IS NOT NULL
460
+ OR json_extract(fact.evidence_json,
461
+ '$.serviceBindingReference.variableName') IS NOT NULL))
462
+ )`, workspaceId);
463
+ }
464
+
465
+ function handlerStatusCount(db: Db, workspaceId?: number): number {
466
+ return count(db, `SELECT COUNT(*) count FROM outbound_calls fact
467
+ JOIN repositories r ON r.id=fact.repo_id
468
+ WHERE ${currentRepositoryPredicate()}
469
+ AND fact.call_type='async_subscribe'
470
+ AND (
471
+ COALESCE(json_extract(fact.evidence_json,
472
+ '$.handlerReferenceStatus'),'')
473
+ NOT IN ('role_required','unsupported_inline','unsupported_wrapper',
474
+ 'unsupported_reference_shape','missing_argument')
475
+ OR (json_extract(fact.evidence_json,
476
+ '$.handlerReferenceStatus')='role_required' AND (
477
+ json_extract(fact.evidence_json,
478
+ '$.handlerReferenceReason') IS NOT NULL
479
+ OR COALESCE(json_extract(fact.evidence_json,
480
+ '$.handlerReferenceShape'),'') NOT IN (
481
+ 'identifier','namespace_member','static_member','default_member',
482
+ 'wrapped_identifier','wrapped_namespace_member',
483
+ 'wrapped_static_member','wrapped_default_member')))
484
+ OR (json_extract(fact.evidence_json,
485
+ '$.handlerReferenceStatus')='unsupported_inline' AND (
486
+ COALESCE(json_extract(fact.evidence_json,
487
+ '$.handlerReferenceReason'),'')<>'inline_handler_body_not_indexed'
488
+ OR COALESCE(json_extract(fact.evidence_json,
489
+ '$.handlerReferenceShape'),'')<>'inline_callback'))
490
+ OR (json_extract(fact.evidence_json,
491
+ '$.handlerReferenceStatus')='unsupported_wrapper' AND (
492
+ COALESCE(json_extract(fact.evidence_json,
493
+ '$.handlerReferenceReason'),'') NOT IN (
494
+ 'wrapper_requires_one_reference',
495
+ 'wrapper_reference_shape_unsupported')
496
+ OR COALESCE(json_extract(fact.evidence_json,
497
+ '$.handlerReferenceShape'),'')<>'wrapper_call'))
498
+ OR (json_extract(fact.evidence_json,
499
+ '$.handlerReferenceStatus')='unsupported_reference_shape' AND (
500
+ COALESCE(json_extract(fact.evidence_json,
501
+ '$.handlerReferenceReason'),'')
502
+ <>'handler_reference_shape_unsupported'
503
+ OR COALESCE(json_extract(fact.evidence_json,
504
+ '$.handlerReferenceShape'),'')<>'unsupported_expression'))
505
+ OR (json_extract(fact.evidence_json,
506
+ '$.handlerReferenceStatus')='missing_argument' AND (
507
+ COALESCE(json_extract(fact.evidence_json,
508
+ '$.handlerReferenceReason'),'')<>'handler_argument_missing'
509
+ OR COALESCE(json_extract(fact.evidence_json,
510
+ '$.handlerReferenceShape'),'')<>'missing'))
511
+ )`, workspaceId);
512
+ }
513
+
514
+ function handlerAssociationCount(db: Db, workspaceId?: number): number {
515
+ return count(db, `SELECT COUNT(*) count FROM (
516
+ SELECT fact.id,
517
+ json_extract(fact.evidence_json,
518
+ '$.handlerReferenceStatus') handler_status,
519
+ COUNT(role.id) role_count
520
+ FROM outbound_calls fact JOIN repositories r ON r.id=fact.repo_id
521
+ LEFT JOIN symbol_calls role ON role.repo_id=fact.repo_id
522
+ AND role.source_file=fact.source_file
523
+ AND role.call_site_start_offset=fact.call_site_start_offset
524
+ AND role.call_site_end_offset=fact.call_site_end_offset
525
+ AND role.call_role='event_subscribe_handler'
526
+ WHERE ${currentRepositoryPredicate()}
527
+ AND fact.call_type='async_subscribe'
528
+ GROUP BY fact.id HAVING
529
+ (handler_status='role_required' AND role_count<>1)
530
+ OR (handler_status<>'role_required' AND role_count<>0)
531
+ )`, workspaceId);
532
+ }
533
+
534
+ function handlerRoleCount(db: Db, workspaceId?: number): number {
535
+ return count(db, `SELECT COUNT(*) count FROM symbol_calls fact
536
+ JOIN repositories r ON r.id=fact.repo_id
537
+ LEFT JOIN outbound_calls subscription
538
+ ON subscription.repo_id=fact.repo_id
539
+ AND subscription.source_file=fact.source_file
540
+ AND subscription.call_site_start_offset=fact.call_site_start_offset
541
+ AND subscription.call_site_end_offset=fact.call_site_end_offset
542
+ AND subscription.call_type='async_subscribe'
543
+ WHERE ${currentRepositoryPredicate()}
544
+ AND fact.call_role='event_subscribe_handler'
545
+ AND (subscription.id IS NULL
546
+ OR subscription.source_line<>fact.source_line
547
+ OR subscription.source_symbol_id IS NOT fact.caller_symbol_id
548
+ OR COALESCE(json_extract(fact.evidence_json,'$.factOrigin'),'')
549
+ <>'event_subscribe_handler_reference'
550
+ OR typeof(json_extract(fact.evidence_json,'$.candidateStrategy'))
551
+ <>'text'
552
+ OR length(json_extract(fact.evidence_json,'$.candidateStrategy'))=0)`,
553
+ workspaceId);
554
+ }
555
+
556
+ function eventAssociationCategories(
557
+ db: Db,
558
+ workspaceId?: number,
559
+ ): FactSemanticCategoryCount[] {
560
+ return [
561
+ ...category('subscription_handler_status_invalid',
562
+ handlerStatusCount(db, workspaceId)),
563
+ ...category('subscription_handler_cardinality_invalid',
564
+ handlerAssociationCount(db, workspaceId)),
565
+ ...category('event_handler_role_provenance_invalid',
566
+ handlerRoleCount(db, workspaceId)),
567
+ ];
568
+ }
569
+
570
+ const symbolResolutionSql = `SELECT COUNT(*) count FROM symbol_calls fact
571
+ JOIN repositories r ON r.id=fact.repo_id
572
+ LEFT JOIN symbols target ON target.id=fact.callee_symbol_id
573
+ LEFT JOIN repositories target_repo ON target_repo.id=target.repo_id
574
+ WHERE ${currentRepositoryPredicate()}
575
+ AND (fact.status NOT IN ('resolved','ambiguous','unresolved')
576
+ OR typeof(json_extract(fact.evidence_json,'$.candidateStrategy'))
577
+ <>'text'
578
+ OR length(json_extract(fact.evidence_json,'$.candidateStrategy'))=0
579
+ OR json_extract(fact.evidence_json,'$.candidateStrategy')
580
+ NOT IN (${sqlTextList(symbolStrategies)})
581
+ OR typeof(json_extract(fact.evidence_json,'$.candidateCount'))
582
+ <>'integer'
583
+ OR typeof(json_extract(fact.evidence_json,'$.eligibleCandidateCount'))
584
+ <>'integer'
585
+ OR typeof(json_extract(fact.evidence_json,'$.selectedCandidateCount'))
586
+ <>'integer'
587
+ OR COALESCE(json_type(
588
+ fact.evidence_json,'$.candidateSetComplete'),'missing')
589
+ NOT IN ('true','false')
590
+ OR json_extract(fact.evidence_json,'$.candidateCount')<0
591
+ OR json_extract(fact.evidence_json,'$.eligibleCandidateCount')<0
592
+ OR json_extract(fact.evidence_json,'$.selectedCandidateCount')
593
+ NOT IN (0,1)
594
+ OR json_extract(fact.evidence_json,'$.selectedCandidateCount')
595
+ >json_extract(fact.evidence_json,'$.eligibleCandidateCount')
596
+ OR json_extract(fact.evidence_json,'$.eligibleCandidateCount')
597
+ >json_extract(fact.evidence_json,'$.candidateCount')
598
+ OR fact.unresolved_reason IS NOT json_extract(
599
+ fact.evidence_json,'$.unresolvedReason')
600
+ OR (fact.status='resolved' AND (
601
+ fact.callee_symbol_id IS NULL OR target.id IS NULL
602
+ OR target_repo.workspace_id IS NOT r.workspace_id
603
+ OR json_extract(fact.evidence_json,'$.candidateSetComplete')<>1
604
+ OR json_extract(fact.evidence_json,'$.eligibleCandidateCount')<>1
605
+ OR json_extract(fact.evidence_json,'$.selectedCandidateCount')<>1
606
+ OR fact.unresolved_reason IS NOT NULL
607
+ OR COALESCE(json_type(
608
+ fact.evidence_json,'$.unresolvedReason'),'missing')<>'null'))
609
+ OR (fact.status='ambiguous' AND (
610
+ fact.callee_symbol_id IS NOT NULL
611
+ OR json_extract(fact.evidence_json,'$.candidateSetComplete')<>1
612
+ OR json_extract(fact.evidence_json,'$.eligibleCandidateCount')<2
613
+ OR json_extract(fact.evidence_json,'$.selectedCandidateCount')<>0
614
+ OR typeof(fact.unresolved_reason)<>'text'
615
+ OR length(fact.unresolved_reason)=0
616
+ OR COALESCE(json_type(
617
+ fact.evidence_json,'$.unresolvedReason'),'missing')<>'text'
618
+ OR fact.unresolved_reason NOT IN (${sqlTextList(symbolReasons)})))
619
+ OR (fact.status='unresolved' AND (
620
+ fact.callee_symbol_id IS NOT NULL
621
+ OR json_extract(fact.evidence_json,'$.selectedCandidateCount')<>0
622
+ OR typeof(fact.unresolved_reason)<>'text'
623
+ OR length(fact.unresolved_reason)=0
624
+ OR COALESCE(json_type(
625
+ fact.evidence_json,'$.unresolvedReason'),'missing')<>'text'
626
+ OR fact.unresolved_reason NOT IN (${sqlTextList(symbolReasons)})
627
+ OR (json_extract(fact.evidence_json,'$.candidateSetComplete')=1
628
+ AND json_extract(fact.evidence_json,
629
+ '$.eligibleCandidateCount')<>0)
630
+ OR (json_extract(fact.evidence_json,'$.candidateSetComplete')=0
631
+ AND fact.unresolved_reason NOT IN (
632
+ ${sqlTextList(incompleteReasons)}))))
633
+ OR (json_extract(fact.evidence_json,'$.candidateStrategy')
634
+ ='package_import_pending'
635
+ AND (COALESCE(json_extract(fact.evidence_json,'$.relation'),'')
636
+ <>'package_import'
637
+ OR COALESCE(json_extract(fact.evidence_json,
638
+ '$.importBinding.moduleKind'),'')<>'package'))
639
+ OR (json_extract(fact.evidence_json,'$.candidateStrategy')
640
+ IN (${sqlTextList(packageStrategies)})
641
+ AND (COALESCE(json_extract(
642
+ fact.evidence_json,'$.relation'),'')<>'package_import'
643
+ OR COALESCE(json_extract(fact.evidence_json,
644
+ '$.importBinding.moduleKind'),'')<>'package'))
645
+ OR (json_extract(fact.evidence_json,'$.candidateStrategy')
646
+ ='package_import_derived_member_unsupported'
647
+ AND (COALESCE(json_extract(fact.evidence_json,'$.relation'),'')
648
+ <>'package_import_derived_member'
649
+ OR COALESCE(json_extract(fact.evidence_json,
650
+ '$.derivedImportBinding.moduleKind'),'')<>'package'))
651
+ OR (json_extract(fact.evidence_json,'$.candidateStrategy')
652
+ IN (${sqlTextList(relativeStrategies)})
653
+ AND (COALESCE(json_extract(fact.evidence_json,
654
+ '$.importBinding.moduleKind'),'')<>'relative'
655
+ OR COALESCE(fact.import_source,'') NOT LIKE '.%'))
656
+ OR (json_extract(fact.evidence_json,'$.candidateStrategy')
657
+ IN (${sqlTextList(simpleStrategies)})
658
+ AND (fact.import_source IS NOT NULL
659
+ OR COALESCE(json_extract(fact.evidence_json,
660
+ '$.importBinding.moduleKind'),'') IN ('relative','package')
661
+ OR COALESCE(json_extract(fact.evidence_json,'$.relation'),'')
662
+ IN ('package_import','package_import_derived_member')))
663
+ __PACKAGE_PENDING_TERMINAL_CHECK__)`;
664
+
665
+ function symbolResolutionCount(
666
+ db: Db,
667
+ workspaceId: number | undefined,
668
+ phase: PackageFactPhase,
669
+ ): number {
670
+ const pendingInvalid = phase === 'terminal'
671
+ ? "OR json_extract(fact.evidence_json,'$.candidateStrategy')='package_import_pending'"
672
+ : '';
673
+ return count(db, symbolResolutionSql.replace(
674
+ '__PACKAGE_PENDING_TERMINAL_CHECK__', pendingInvalid,
675
+ ), workspaceId);
676
+ }
677
+
678
+ export function invalidFactSemanticCategories(
679
+ db: Db,
680
+ workspaceId?: number,
681
+ phase: PackageFactPhase = 'pre_package',
682
+ ): FactSemanticCategoryCount[] {
683
+ return [
684
+ ...repositoryCategories(db, workspaceId),
685
+ ...operationCategories(db, workspaceId),
686
+ ...callSpanCategories(db, workspaceId),
687
+ ...duplicateSymbolCallCategories(db, workspaceId),
688
+ ...eventNameCategories(db, workspaceId),
689
+ ...ownerCategories(db, workspaceId),
690
+ ...bindingCategories(db, workspaceId),
691
+ ...invalidBindingFactCategories(db, workspaceId),
692
+ ...eventAssociationCategories(db, workspaceId),
693
+ ...invalidPackageFactCategories(db, workspaceId, phase),
694
+ ...invalidRelativeFactCategories(db, workspaceId),
695
+ ...invalidSymbolFactCategories(db, workspaceId),
696
+ ...category('symbol_call_resolution_matrix_invalid',
697
+ symbolResolutionCount(db, workspaceId, phase)),
698
+ ];
699
+ }