@saptools/service-flow 0.1.67 → 0.1.68

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 (80) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +20 -8
  3. package/TECHNICAL-NOTE.md +27 -0
  4. package/dist/{chunk-ZQABU7MR.js → chunk-AEM4JY22.js} +12676 -6714
  5. package/dist/chunk-AEM4JY22.js.map +1 -0
  6. package/dist/cli.js +2325 -413
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +61 -2
  9. package/dist/index.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cli/003-doctor-package-resolution.ts +68 -0
  12. package/src/cli/doctor.ts +25 -14
  13. package/src/db/000-call-fact-repository.ts +475 -342
  14. package/src/db/001-fact-lifecycle.ts +60 -30
  15. package/src/db/002-fact-json-inventory.ts +169 -0
  16. package/src/db/003-current-fact-semantics.ts +698 -0
  17. package/src/db/004-package-target-invalidation.ts +173 -0
  18. package/src/db/005-schema-structure.ts +201 -0
  19. package/src/db/006-relative-symbol-resolution.ts +443 -0
  20. package/src/db/007-package-fact-semantics.ts +573 -0
  21. package/src/db/008-relative-fact-semantics.ts +207 -0
  22. package/src/db/009-binding-fact-semantics.ts +347 -0
  23. package/src/db/010-package-symbol-surface-semantics.ts +320 -0
  24. package/src/db/011-symbol-call-semantics.ts +144 -0
  25. package/src/db/012-binding-reference-proof.ts +264 -0
  26. package/src/db/migrations.ts +16 -3
  27. package/src/db/repositories.ts +113 -6
  28. package/src/db/schema.ts +4 -2
  29. package/src/index.ts +12 -0
  30. package/src/indexer/repository-indexer.ts +72 -11
  31. package/src/indexer/workspace-indexer.ts +123 -32
  32. package/src/linker/003-package-import-symbol-resolver.ts +363 -131
  33. package/src/linker/004-event-subscription-handler-linker.ts +2 -0
  34. package/src/linker/005-odata-path-structure.ts +371 -0
  35. package/src/linker/cross-repo-linker.ts +2 -1
  36. package/src/linker/odata-path-normalizer.ts +273 -180
  37. package/src/linker/service-resolver.ts +197 -77
  38. package/src/parsers/002-symbol-import-bindings.ts +516 -0
  39. package/src/parsers/003-package-public-surface.ts +661 -0
  40. package/src/parsers/004-fact-identity.ts +108 -0
  41. package/src/parsers/005-event-subscription-facts.ts +281 -0
  42. package/src/parsers/006-binding-identity.ts +343 -0
  43. package/src/parsers/007-source-fact-reconciliation.ts +105 -0
  44. package/src/parsers/008-package-surface-publication.ts +82 -0
  45. package/src/parsers/009-symbol-call-facts.ts +528 -0
  46. package/src/parsers/010-package-public-surface-analysis.ts +352 -0
  47. package/src/parsers/011-binding-lexical-scope.ts +583 -0
  48. package/src/parsers/012-package-fact-contract.ts +306 -0
  49. package/src/parsers/013-executable-body-eligibility.ts +35 -0
  50. package/src/parsers/014-service-binding-helper-flow.ts +240 -0
  51. package/src/parsers/015-service-binding-collector.ts +693 -0
  52. package/src/parsers/016-local-symbol-reference.ts +261 -0
  53. package/src/parsers/017-symbol-derived-contexts.ts +268 -0
  54. package/src/parsers/018-package-commonjs-syntax.ts +142 -0
  55. package/src/parsers/019-binding-assignment-targets.ts +76 -0
  56. package/src/parsers/020-stable-local-value.ts +217 -0
  57. package/src/parsers/021-binding-visibility.ts +152 -0
  58. package/src/parsers/operation-path-analysis.ts +6 -1
  59. package/src/parsers/outbound-call-parser.ts +19 -6
  60. package/src/parsers/package-json-parser.ts +45 -3
  61. package/src/parsers/service-binding-parser-helpers.ts +86 -15
  62. package/src/parsers/service-binding-parser.ts +147 -597
  63. package/src/parsers/symbol-parser.ts +482 -353
  64. package/src/trace/002-trace-diagnostics.ts +36 -1
  65. package/src/trace/016-compact-projector.ts +16 -17
  66. package/src/trace/020-compact-field-projection.ts +48 -37
  67. package/src/trace/021-compact-decision-normalization.ts +105 -0
  68. package/src/trace/022-trace-fact-preflight.ts +21 -0
  69. package/src/trace/023-nested-event-scopes.ts +23 -0
  70. package/src/trace/024-compact-observation-decision.ts +76 -0
  71. package/src/trace/025-trace-implementation-scope.ts +123 -0
  72. package/src/trace/026-trace-start-scope.ts +335 -0
  73. package/src/trace/027-trace-scope-execution.ts +566 -0
  74. package/src/trace/028-trace-operation-execution.ts +336 -0
  75. package/src/trace/029-trace-start-implementation.ts +172 -0
  76. package/src/trace/trace-engine.ts +122 -624
  77. package/src/types.ts +56 -0
  78. package/src/utils/001-placeholders.ts +188 -10
  79. package/src/version.ts +1 -1
  80. package/dist/chunk-ZQABU7MR.js.map +0 -1
@@ -0,0 +1,573 @@
1
+ import type { Db } from './connection.js';
2
+ import { ANALYZER_VERSION } from '../version.js';
3
+ import type {
4
+ PackagePublicScope,
5
+ PackagePublicSurfaceFact,
6
+ PublicSurfaceTarget,
7
+ } from '../parsers/003-package-public-surface.js';
8
+ import {
9
+ parsePackageImportReference,
10
+ parsePackagePublicSurfaceFact,
11
+ } from '../parsers/012-package-fact-contract.js';
12
+ import type { SymbolImportReference } from
13
+ '../parsers/002-symbol-import-bindings.js';
14
+ import type { PackageFactPhase } from './003-current-fact-semantics.js';
15
+ import {
16
+ expectedPackageImportResolutions,
17
+ type PackageCallResolution,
18
+ } from '../linker/003-package-import-symbol-resolver.js';
19
+ import {
20
+ invalidPackageSymbolSurfaceCount,
21
+ } from './010-package-symbol-surface-semantics.js';
22
+
23
+ export interface PackageFactCategoryCount {
24
+ category: string;
25
+ count: number;
26
+ }
27
+
28
+ interface PackageCallRow {
29
+ id: number;
30
+ workspaceId: number;
31
+ importSource: string | null;
32
+ status: string;
33
+ unresolvedReason: string | null;
34
+ calleeSymbolId: number | null;
35
+ evidence: Record<string, unknown>;
36
+ }
37
+
38
+ const unresolvedReasons = new Set([
39
+ 'package_repository_scope_ambiguous',
40
+ 'package_repository_not_indexed',
41
+ 'package_public_surface_unsupported',
42
+ 'public_surface_evidence_incomplete',
43
+ 'package_binding_type_only',
44
+ 'package_public_scope_duplicate',
45
+ 'package_public_name_not_exposed',
46
+ 'public_symbol_has_no_executable_body',
47
+ ]);
48
+
49
+ function record(value: unknown): Record<string, unknown> | undefined {
50
+ return value && typeof value === 'object' && !Array.isArray(value)
51
+ ? value as Record<string, unknown> : undefined;
52
+ }
53
+
54
+ function parseRecord(value: unknown): Record<string, unknown> | undefined {
55
+ if (typeof value !== 'string') return undefined;
56
+ try {
57
+ return record(JSON.parse(value) as unknown);
58
+ } catch {
59
+ return undefined;
60
+ }
61
+ }
62
+
63
+ function currentPredicate(alias: string): string {
64
+ return `${alias}.fact_analyzer_version=?
65
+ AND (? IS NULL OR ${alias}.workspace_id=?)`;
66
+ }
67
+
68
+ function category(
69
+ name: string,
70
+ count: number,
71
+ ): PackageFactCategoryCount[] {
72
+ return count > 0 ? [{ category: name, count }] : [];
73
+ }
74
+
75
+ function repositorySurfaceInvalidCount(
76
+ db: Db,
77
+ workspaceId?: number,
78
+ ): number {
79
+ const rows = db.prepare(`SELECT package_name packageName,
80
+ package_public_surface_json surfaceJson FROM repositories r
81
+ WHERE ${currentPredicate('r')}`).all(
82
+ ANALYZER_VERSION, workspaceId, workspaceId,
83
+ );
84
+ return rows.filter((row) => {
85
+ const packageName = typeof row.packageName === 'string'
86
+ ? row.packageName : null;
87
+ const parsed = parseRecord(row.surfaceJson);
88
+ return !parsed
89
+ || !parsePackagePublicSurfaceFact(parsed, packageName);
90
+ }).length;
91
+ }
92
+
93
+ function packageCallRows(
94
+ db: Db,
95
+ workspaceId?: number,
96
+ ): PackageCallRow[] {
97
+ const rows = db.prepare(`SELECT sc.id,r.workspace_id workspaceId,
98
+ sc.import_source importSource,sc.status,sc.unresolved_reason unresolvedReason,
99
+ sc.callee_symbol_id calleeSymbolId,sc.evidence_json evidenceJson
100
+ FROM symbol_calls sc JOIN repositories r ON r.id=sc.repo_id
101
+ WHERE ${currentPredicate('r')}
102
+ AND (json_extract(sc.evidence_json,'$.relation')='package_import'
103
+ OR json_extract(sc.evidence_json,
104
+ '$.importBinding.moduleKind')='package')
105
+ ORDER BY sc.id`).all(ANALYZER_VERSION, workspaceId, workspaceId);
106
+ return rows.flatMap((row) => {
107
+ const evidence = parseRecord(row.evidenceJson);
108
+ return typeof row.id === 'number' && typeof row.workspaceId === 'number'
109
+ && typeof row.status === 'string' && evidence
110
+ ? [{
111
+ id: row.id,
112
+ workspaceId: row.workspaceId,
113
+ importSource: typeof row.importSource === 'string'
114
+ ? row.importSource : null,
115
+ status: row.status,
116
+ unresolvedReason: typeof row.unresolvedReason === 'string'
117
+ ? row.unresolvedReason : null,
118
+ calleeSymbolId: typeof row.calleeSymbolId === 'number'
119
+ ? row.calleeSymbolId : null,
120
+ evidence,
121
+ }]
122
+ : [];
123
+ });
124
+ }
125
+
126
+ function nonRelativeImportInvalidCount(
127
+ db: Db,
128
+ workspaceId?: number,
129
+ ): number {
130
+ const rows = db.prepare(`SELECT sc.status,
131
+ sc.unresolved_reason unresolvedReason,
132
+ sc.callee_symbol_id calleeSymbolId,sc.import_source importSource,
133
+ sc.evidence_json evidenceJson
134
+ FROM symbol_calls sc JOIN repositories r ON r.id=sc.repo_id
135
+ WHERE ${currentPredicate('r')}
136
+ AND sc.import_source IS NOT NULL
137
+ AND sc.import_source NOT LIKE '.%'`).all(
138
+ ANALYZER_VERSION, workspaceId, workspaceId,
139
+ );
140
+ return rows.filter((row) => !nonRelativeImportValid(row)).length;
141
+ }
142
+
143
+ function nonRelativeImportValid(
144
+ row: Record<string, unknown>,
145
+ ): boolean {
146
+ const evidence = parseRecord(row.evidenceJson);
147
+ if (!evidence) return false;
148
+ const direct = parsePackageImportReference(evidence.importBinding);
149
+ if (direct) return all([
150
+ evidence.relation === 'package_import',
151
+ evidence.derivedImportBinding === undefined,
152
+ row.importSource === direct.rawModuleSpecifier,
153
+ evidence.targetName === direct.requestedPublicName,
154
+ ]);
155
+ return !derivedPackageRowInvalid(row);
156
+ }
157
+
158
+ function integerField(
159
+ value: Record<string, unknown>,
160
+ key: string,
161
+ ): number | undefined {
162
+ const item = value[key];
163
+ return Number.isInteger(item) && Number(item) >= 0
164
+ ? Number(item) : undefined;
165
+ }
166
+
167
+ function all(values: readonly boolean[]): boolean {
168
+ return values.every(Boolean);
169
+ }
170
+
171
+ function pendingValid(row: PackageCallRow): boolean {
172
+ const evidence = row.evidence;
173
+ return all([
174
+ row.status === 'unresolved',
175
+ row.calleeSymbolId === null,
176
+ row.unresolvedReason === 'package_resolution_pending',
177
+ evidence.candidateStrategy === 'package_import_pending',
178
+ integerField(evidence, 'candidateCount') === 0,
179
+ integerField(evidence, 'eligibleCandidateCount') === 0,
180
+ integerField(evidence, 'selectedCandidateCount') === 0,
181
+ evidence.candidateSetComplete === false,
182
+ evidence.unresolvedReason === row.unresolvedReason,
183
+ ...pendingDetailsAbsent(evidence),
184
+ ]);
185
+ }
186
+
187
+ function pendingDetailsAbsent(
188
+ evidence: Record<string, unknown>,
189
+ ): boolean[] {
190
+ return [
191
+ evidence.resolvedModulePath === undefined,
192
+ evidence.resolvedTargetRepositoryId === undefined,
193
+ evidence.targetRepositoryCandidateCount === undefined,
194
+ evidence.targetRepositoryCandidates === undefined,
195
+ evidence.shownTargetRepositoryCandidateCount === undefined,
196
+ evidence.omittedTargetRepositoryCandidateCount === undefined,
197
+ evidence.publicSurface === undefined,
198
+ ];
199
+ }
200
+
201
+ function repositoryReferencesValid(
202
+ evidence: Record<string, unknown>,
203
+ ): boolean {
204
+ const total = integerField(evidence, 'targetRepositoryCandidateCount');
205
+ const shown = integerField(
206
+ evidence, 'shownTargetRepositoryCandidateCount',
207
+ );
208
+ const omitted = integerField(
209
+ evidence, 'omittedTargetRepositoryCandidateCount',
210
+ );
211
+ const ids = evidence.targetRepositoryCandidates;
212
+ return total !== undefined && shown !== undefined && omitted !== undefined
213
+ && Array.isArray(ids) && ids.every((id) =>
214
+ Number.isInteger(id) && Number(id) > 0)
215
+ && new Set(ids).size === ids.length && ids.length === shown
216
+ && shown <= 5 && shown + omitted === total;
217
+ }
218
+
219
+ function terminalMatrixValid(row: PackageCallRow): boolean {
220
+ const strategy = row.evidence.candidateStrategy;
221
+ const evidenceReason = row.evidence.unresolvedReason;
222
+ if (!repositoryReferencesValid(row.evidence)
223
+ || evidenceReason !== row.unresolvedReason) return false;
224
+ if (row.status === 'resolved') return all([
225
+ strategy === 'package_public_surface_exact',
226
+ row.unresolvedReason === null,
227
+ row.calleeSymbolId !== null,
228
+ ]);
229
+ if (row.status === 'ambiguous') return all([
230
+ strategy === 'package_public_surface_ambiguous',
231
+ row.unresolvedReason === 'package_public_target_ambiguous',
232
+ row.calleeSymbolId === null,
233
+ ]);
234
+ return unresolvedMatrixValid(row, strategy);
235
+ }
236
+
237
+ function unresolvedMatrixValid(
238
+ row: PackageCallRow,
239
+ strategy: unknown,
240
+ ): boolean {
241
+ return all([
242
+ row.status === 'unresolved',
243
+ strategy === 'package_public_surface_unresolved',
244
+ typeof row.unresolvedReason === 'string',
245
+ unresolvedReasons.has(String(row.unresolvedReason)),
246
+ row.calleeSymbolId === null,
247
+ ]);
248
+ }
249
+
250
+ function targetRepositories(
251
+ db: Db,
252
+ workspaceId: number,
253
+ binding: SymbolImportReference,
254
+ ): Array<{ id: number; surface: PackagePublicSurfaceFact }> {
255
+ const rows = db.prepare(`SELECT id,package_public_surface_json surfaceJson
256
+ FROM repositories WHERE workspace_id=? AND package_name=?
257
+ ORDER BY id`).all(workspaceId, binding.requestedPackageName);
258
+ return rows.flatMap((row) => {
259
+ const value = parseRecord(row.surfaceJson);
260
+ const surface = value
261
+ ? parsePackagePublicSurfaceFact(value, binding.requestedPackageName)
262
+ : undefined;
263
+ return typeof row.id === 'number' && surface
264
+ ? [{ id: row.id, surface }] : [];
265
+ });
266
+ }
267
+
268
+ function exactPublicScope(
269
+ surface: PackagePublicSurfaceFact,
270
+ binding: SymbolImportReference,
271
+ ): PackagePublicScope | undefined {
272
+ if (surface.status !== 'complete') return undefined;
273
+ const scopes = surface.scopes.filter((scope) =>
274
+ scope.entry === binding.requestedModuleSubpath
275
+ && scope.publicName === binding.requestedPublicName);
276
+ return scopes.length === 1 ? scopes[0] : undefined;
277
+ }
278
+
279
+ function selectedTarget(
280
+ db: Db,
281
+ symbolId: number,
282
+ repositoryId: number,
283
+ scope: PackagePublicScope,
284
+ ): PublicSurfaceTarget | undefined {
285
+ const row = db.prepare(`SELECT source_file sourceFile,kind,
286
+ qualified_name qualifiedName,start_offset startOffset,
287
+ end_offset endOffset,evidence_json evidenceJson FROM symbols
288
+ WHERE id=? AND repo_id=?`).get(symbolId, repositoryId);
289
+ if (!row) return undefined;
290
+ return scope.targets.find((target) =>
291
+ target.bodyEligibility.eligible
292
+ && target.sourceFile === row.sourceFile && target.kind === row.kind
293
+ && target.qualifiedName === row.qualifiedName
294
+ && target.startOffset === row.startOffset && target.endOffset === row.endOffset
295
+ && symbolExposureValid(row.evidenceJson, target, scope));
296
+ }
297
+
298
+ function symbolExposureValid(
299
+ evidenceJson: unknown,
300
+ target: PublicSurfaceTarget,
301
+ scope: PackagePublicScope,
302
+ ): boolean {
303
+ const evidence = parseRecord(evidenceJson);
304
+ if (!evidence) return false;
305
+ const surface = record(evidence.packagePublicSurface);
306
+ if (!surface) return false;
307
+ const body = record(surface.bodyEligibility);
308
+ if (!body) return false;
309
+ const exposures = surface.exposures;
310
+ if (!Array.isArray(exposures)) return false;
311
+ if (!symbolExposureMetadataValid(
312
+ surface, body, target, exposures.length,
313
+ )) return false;
314
+ return exposures.some((value) => exposureMatches(value, scope));
315
+ }
316
+
317
+ function symbolExposureMetadataValid(
318
+ surface: Record<string, unknown>,
319
+ body: Record<string, unknown>,
320
+ target: PublicSurfaceTarget,
321
+ exposureCount: number,
322
+ ): boolean {
323
+ return all([
324
+ surface.schema === 'service-flow/package-public-surface@1',
325
+ body.eligible === true,
326
+ body.reason === target.bodyEligibility.reason,
327
+ surface.recordCap === 256,
328
+ surface.exposureTotal === exposureCount,
329
+ surface.shownExposureCount === exposureCount,
330
+ surface.omittedExposureCount === 0,
331
+ ]);
332
+ }
333
+
334
+ function exposureMatches(
335
+ value: unknown,
336
+ scope: PackagePublicScope,
337
+ ): boolean {
338
+ const exposure = record(value);
339
+ return all([
340
+ exposure?.entry === scope.entry,
341
+ exposure?.modulePath === scope.modulePath,
342
+ exposure?.publicName === scope.publicName,
343
+ ]);
344
+ }
345
+
346
+ function resolvedCountsValid(
347
+ evidence: Record<string, unknown>,
348
+ scope: PackagePublicScope,
349
+ repositoryId: number,
350
+ ): boolean {
351
+ return evidence.resolvedModulePath === scope.modulePath
352
+ && evidence.resolvedTargetRepositoryId === repositoryId
353
+ && integerField(evidence, 'candidateCount') === scope.candidateCount
354
+ && integerField(evidence, 'eligibleCandidateCount')
355
+ === scope.eligibleCandidateCount
356
+ && integerField(evidence, 'selectedCandidateCount') === 1
357
+ && evidence.candidateSetComplete === true
358
+ && integerField(evidence, 'targetRepositoryCandidateCount') === 1;
359
+ }
360
+
361
+ function resolvedProofValid(
362
+ db: Db,
363
+ row: PackageCallRow,
364
+ binding: SymbolImportReference,
365
+ ): boolean {
366
+ if (row.calleeSymbolId === null) return false;
367
+ const repositories = targetRepositories(db, row.workspaceId, binding);
368
+ if (repositories.length !== 1 || !repositories[0]) return false;
369
+ const repository = repositories[0];
370
+ const scope = exactPublicScope(repository.surface, binding);
371
+ if (!scope || scope.eligibleCandidateCount !== 1) return false;
372
+ return Boolean(selectedTarget(
373
+ db, row.calleeSymbolId, repository.id, scope,
374
+ )) && resolvedCountsValid(row.evidence, scope, repository.id);
375
+ }
376
+
377
+ function publicSurfaceMatches(
378
+ evidence: Record<string, unknown>,
379
+ expected: PackageCallResolution,
380
+ ): boolean {
381
+ const actual = record(evidence.publicSurface);
382
+ if (!expected.publicSurface) return actual === undefined;
383
+ return actual?.total === expected.publicSurface.total
384
+ && actual.shown === expected.publicSurface.shown
385
+ && actual.omitted === expected.publicSurface.omitted;
386
+ }
387
+
388
+ function repositoryCandidatesMatch(
389
+ evidence: Record<string, unknown>,
390
+ expected: PackageCallResolution,
391
+ ): boolean {
392
+ const shown = expected.repositoryCandidateIds.slice(0, 5);
393
+ const actual = evidence.targetRepositoryCandidates;
394
+ return evidence.targetRepositoryCandidateCount
395
+ === expected.repositoryCandidateIds.length
396
+ && evidence.shownTargetRepositoryCandidateCount === shown.length
397
+ && evidence.omittedTargetRepositoryCandidateCount
398
+ === expected.repositoryCandidateIds.length - shown.length
399
+ && Array.isArray(actual)
400
+ && actual.length === shown.length
401
+ && shown.every((id, index) =>
402
+ actual[index] === id);
403
+ }
404
+
405
+ function expectedResolutionMatches(
406
+ row: PackageCallRow,
407
+ expected: PackageCallResolution,
408
+ ): boolean {
409
+ const evidence = row.evidence;
410
+ return all([
411
+ row.status === expected.status,
412
+ row.calleeSymbolId === expected.id,
413
+ row.unresolvedReason === expected.reason,
414
+ evidence.candidateStrategy === expected.strategy,
415
+ integerField(evidence, 'candidateCount') === expected.candidateCount,
416
+ integerField(evidence, 'eligibleCandidateCount')
417
+ === expected.eligibleCandidateCount,
418
+ integerField(evidence, 'selectedCandidateCount')
419
+ === expected.selectedCandidateCount,
420
+ evidence.candidateSetComplete === expected.candidateSetComplete,
421
+ evidence.unresolvedReason === expected.reason,
422
+ evidence.resolvedModulePath === expected.resolvedModulePath,
423
+ evidence.resolvedTargetRepositoryId === expected.targetRepoId,
424
+ repositoryCandidatesMatch(evidence, expected),
425
+ publicSurfaceMatches(evidence, expected),
426
+ ]);
427
+ }
428
+
429
+ function expectedByCall(
430
+ db: Db,
431
+ rows: readonly PackageCallRow[],
432
+ bindings: ReadonlyMap<number, SymbolImportReference>,
433
+ ): Map<number, PackageCallResolution> {
434
+ const result = new Map<number, PackageCallResolution>();
435
+ const workspaces = new Set(rows.map((row) => row.workspaceId));
436
+ for (const workspaceId of workspaces) {
437
+ const inputs = rows.filter((row) => row.workspaceId === workspaceId)
438
+ .flatMap((row) => {
439
+ const binding = bindings.get(row.id);
440
+ return binding ? [{ callId: row.id, binding }] : [];
441
+ });
442
+ for (const expected of expectedPackageImportResolutions(
443
+ db, workspaceId, inputs,
444
+ )) result.set(expected.callId, expected);
445
+ }
446
+ return result;
447
+ }
448
+
449
+ function invalidPackageCallCount(
450
+ db: Db,
451
+ workspaceId: number | undefined,
452
+ phase: PackageFactPhase,
453
+ ): number {
454
+ const rows = packageCallRows(db, workspaceId);
455
+ const bindings = new Map(rows.flatMap((row) => {
456
+ const binding = parsePackageImportReference(row.evidence.importBinding);
457
+ return binding ? [[row.id, binding] as const] : [];
458
+ }));
459
+ const expected = expectedByCall(db, rows, bindings);
460
+ return rows.filter((row) => packageCallInvalid(
461
+ db, row, bindings.get(row.id), expected.get(row.id), phase,
462
+ )).length;
463
+ }
464
+
465
+ function packageCallInvalid(
466
+ db: Db,
467
+ row: PackageCallRow,
468
+ binding: SymbolImportReference | undefined,
469
+ expected: PackageCallResolution | undefined,
470
+ phase: PackageFactPhase,
471
+ ): boolean {
472
+ if (!binding || !packageProvenanceValid(row, binding)) return true;
473
+ if (row.evidence.candidateStrategy === 'package_import_pending')
474
+ return phase === 'terminal' || !pendingValid(row);
475
+ if (!terminalMatrixValid(row) || !expected) return true;
476
+ if (!expectedResolutionMatches(row, expected)) return true;
477
+ return row.status === 'resolved' && !resolvedProofValid(db, row, binding);
478
+ }
479
+
480
+ function packageProvenanceValid(
481
+ row: PackageCallRow,
482
+ binding: SymbolImportReference,
483
+ ): boolean {
484
+ return all([
485
+ row.evidence.relation === 'package_import',
486
+ row.evidence.derivedImportBinding === undefined,
487
+ row.importSource === binding.rawModuleSpecifier,
488
+ row.evidence.targetName === binding.requestedPublicName,
489
+ ]);
490
+ }
491
+
492
+ function derivedPackageInvalidCount(
493
+ db: Db,
494
+ workspaceId?: number,
495
+ ): number {
496
+ const rows = db.prepare(`SELECT sc.status,
497
+ sc.unresolved_reason unresolvedReason,
498
+ sc.callee_symbol_id calleeSymbolId,sc.import_source importSource,
499
+ sc.evidence_json evidenceJson
500
+ FROM symbol_calls sc JOIN repositories r ON r.id=sc.repo_id
501
+ WHERE ${currentPredicate('r')}
502
+ AND (json_extract(sc.evidence_json,'$.relation')
503
+ ='package_import_derived_member'
504
+ OR json_extract(sc.evidence_json,
505
+ '$.derivedImportBinding.moduleKind')='package')`).all(
506
+ ANALYZER_VERSION, workspaceId, workspaceId,
507
+ );
508
+ return rows.filter(derivedPackageRowInvalid).length;
509
+ }
510
+
511
+ function derivedPackageRowInvalid(row: Record<string, unknown>): boolean {
512
+ const evidence = parseRecord(row.evidenceJson);
513
+ const binding = evidence
514
+ ? parsePackageImportReference(evidence.derivedImportBinding)
515
+ : undefined;
516
+ if (!evidence || !binding) return true;
517
+ return !all([
518
+ row.importSource === binding.rawModuleSpecifier,
519
+ evidence.relation === 'package_import_derived_member',
520
+ row.status === 'unresolved',
521
+ row.calleeSymbolId === null,
522
+ row.unresolvedReason === 'package_derived_member_provenance_insufficient',
523
+ evidence.candidateStrategy
524
+ === 'package_import_derived_member_unsupported',
525
+ integerField(evidence, 'candidateCount') === 0,
526
+ integerField(evidence, 'eligibleCandidateCount') === 0,
527
+ integerField(evidence, 'selectedCandidateCount') === 0,
528
+ evidence.candidateSetComplete === true,
529
+ evidence.unresolvedReason === row.unresolvedReason,
530
+ evidence.importBinding === undefined,
531
+ derivedPackageTargetValid(evidence, binding),
532
+ ]);
533
+ }
534
+
535
+ function derivedPackageTargetValid(
536
+ evidence: Record<string, unknown>,
537
+ binding: SymbolImportReference,
538
+ ): boolean {
539
+ if (typeof binding.referencedMemberName !== 'string') return false;
540
+ const proxy = typeof evidence.proxyVariableName === 'string';
541
+ return evidence.targetName === (proxy
542
+ ? binding.referencedMemberName
543
+ : binding.requestedPublicName);
544
+ }
545
+
546
+ export function invalidPackageFactCategories(
547
+ db: Db,
548
+ workspaceId?: number,
549
+ phase: PackageFactPhase = 'pre_package',
550
+ ): PackageFactCategoryCount[] {
551
+ return [
552
+ ...category(
553
+ 'repository_package_public_surface_invalid',
554
+ repositorySurfaceInvalidCount(db, workspaceId),
555
+ ),
556
+ ...category(
557
+ 'symbol_package_public_surface_invalid',
558
+ invalidPackageSymbolSurfaceCount(db, workspaceId),
559
+ ),
560
+ ...category(
561
+ 'package_import_fact_or_target_invalid',
562
+ invalidPackageCallCount(db, workspaceId, phase),
563
+ ),
564
+ ...category(
565
+ 'package_derived_member_fact_invalid',
566
+ derivedPackageInvalidCount(db, workspaceId),
567
+ ),
568
+ ...category(
569
+ 'package_import_provenance_marker_invalid',
570
+ nonRelativeImportInvalidCount(db, workspaceId),
571
+ ),
572
+ ];
573
+ }