@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.
- package/CHANGELOG.md +20 -0
- package/README.md +27 -9
- package/TECHNICAL-NOTE.md +36 -0
- package/dist/chunk-3N3B5KHV.js +19596 -0
- package/dist/chunk-3N3B5KHV.js.map +1 -0
- package/dist/cli.js +2645 -521
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +67 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/001-index-summary.ts +22 -0
- package/src/cli/003-doctor-package-resolution.ts +68 -0
- package/src/cli/doctor.ts +25 -14
- package/src/cli.ts +151 -87
- package/src/db/000-call-fact-repository.ts +499 -340
- package/src/db/001-fact-lifecycle.ts +60 -30
- package/src/db/002-fact-json-inventory.ts +169 -0
- package/src/db/003-current-fact-semantics.ts +699 -0
- package/src/db/004-package-target-invalidation.ts +183 -0
- package/src/db/005-schema-structure.ts +201 -0
- package/src/db/006-relative-symbol-resolution.ts +464 -0
- package/src/db/007-package-fact-semantics.ts +573 -0
- package/src/db/008-relative-fact-semantics.ts +210 -0
- package/src/db/009-binding-fact-semantics.ts +352 -0
- package/src/db/010-package-symbol-surface-semantics.ts +320 -0
- package/src/db/011-symbol-call-semantics.ts +144 -0
- package/src/db/012-binding-reference-proof.ts +268 -0
- package/src/db/013-index-publication-failure.ts +91 -0
- package/src/db/014-binding-helper-provenance.ts +17 -0
- package/src/db/migrations.ts +16 -3
- package/src/db/repositories.ts +130 -6
- package/src/db/schema.ts +4 -2
- package/src/index.ts +12 -0
- package/src/indexer/cds-extension-resolver.ts +27 -3
- package/src/indexer/repository-indexer.ts +135 -13
- package/src/indexer/workspace-indexer.ts +237 -34
- package/src/linker/003-package-import-symbol-resolver.ts +363 -131
- package/src/linker/004-event-subscription-handler-linker.ts +34 -11
- package/src/linker/005-odata-path-structure.ts +371 -0
- package/src/linker/006-event-template-link.ts +72 -0
- package/src/linker/007-call-edge-insertion.ts +568 -0
- package/src/linker/cross-repo-linker.ts +4 -166
- package/src/linker/odata-path-normalizer.ts +273 -180
- package/src/linker/service-resolver.ts +197 -77
- package/src/parsers/000-direct-query-execution.ts +11 -0
- package/src/parsers/002-symbol-import-bindings.ts +516 -0
- package/src/parsers/003-package-public-surface.ts +661 -0
- package/src/parsers/004-fact-identity.ts +108 -0
- package/src/parsers/005-event-subscription-facts.ts +281 -0
- package/src/parsers/006-binding-identity.ts +348 -0
- package/src/parsers/007-source-fact-reconciliation.ts +105 -0
- package/src/parsers/008-package-surface-publication.ts +82 -0
- package/src/parsers/009-symbol-call-facts.ts +528 -0
- package/src/parsers/010-package-public-surface-analysis.ts +352 -0
- package/src/parsers/011-binding-lexical-scope.ts +583 -0
- package/src/parsers/012-package-fact-contract.ts +306 -0
- package/src/parsers/013-executable-body-eligibility.ts +35 -0
- package/src/parsers/014-service-binding-helper-flow.ts +306 -0
- package/src/parsers/015-service-binding-collector.ts +693 -0
- package/src/parsers/016-local-symbol-reference.ts +261 -0
- package/src/parsers/017-symbol-derived-contexts.ts +268 -0
- package/src/parsers/018-package-commonjs-syntax.ts +142 -0
- package/src/parsers/019-binding-assignment-targets.ts +76 -0
- package/src/parsers/020-stable-local-value.ts +217 -0
- package/src/parsers/021-binding-visibility.ts +168 -0
- package/src/parsers/022-outbound-expression-analysis.ts +700 -0
- package/src/parsers/023-outbound-call-classifier.ts +692 -0
- package/src/parsers/operation-path-analysis.ts +6 -1
- package/src/parsers/outbound-call-parser.ts +162 -512
- package/src/parsers/package-json-parser.ts +45 -3
- package/src/parsers/service-binding-parser-helpers.ts +86 -15
- package/src/parsers/service-binding-parser.ts +147 -597
- package/src/parsers/symbol-parser.ts +513 -352
- package/src/trace/002-trace-diagnostics.ts +36 -1
- package/src/trace/007-implementation-start-diagnostic.ts +1 -0
- package/src/trace/011-event-subscriber-traversal.ts +100 -8
- package/src/trace/013-trace-root-scopes.ts +2 -3
- package/src/trace/014-compact-contract.ts +6 -0
- package/src/trace/015-trace-edge-recorder.ts +61 -4
- package/src/trace/016-compact-projector.ts +15 -17
- package/src/trace/019-trace-edge-semantics.ts +6 -10
- package/src/trace/020-compact-field-projection.ts +122 -38
- package/src/trace/021-compact-decision-normalization.ts +171 -0
- package/src/trace/022-trace-fact-preflight.ts +21 -0
- package/src/trace/023-nested-event-scopes.ts +23 -0
- package/src/trace/024-compact-observation-decision.ts +81 -0
- package/src/trace/025-trace-implementation-scope.ts +123 -0
- package/src/trace/026-trace-start-scope.ts +336 -0
- package/src/trace/027-trace-scope-execution.ts +566 -0
- package/src/trace/028-trace-operation-execution.ts +336 -0
- package/src/trace/029-trace-start-implementation.ts +172 -0
- package/src/trace/030-event-runtime-resolution.ts +151 -0
- package/src/trace/031-local-call-expansion.ts +37 -0
- package/src/trace/implementation-hints.ts +9 -6
- package/src/trace/selectors.ts +1 -0
- package/src/trace/trace-engine.ts +122 -624
- package/src/types.ts +57 -0
- package/src/utils/001-placeholders.ts +188 -10
- package/src/version.ts +1 -1
- package/dist/chunk-ZQABU7MR.js +0 -12151
- 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
|
+
}
|