@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.
- package/CHANGELOG.md +10 -0
- package/README.md +20 -8
- package/TECHNICAL-NOTE.md +27 -0
- package/dist/{chunk-ZQABU7MR.js → chunk-AEM4JY22.js} +12676 -6714
- package/dist/chunk-AEM4JY22.js.map +1 -0
- package/dist/cli.js +2325 -413
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +61 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/003-doctor-package-resolution.ts +68 -0
- package/src/cli/doctor.ts +25 -14
- package/src/db/000-call-fact-repository.ts +475 -342
- 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 +698 -0
- package/src/db/004-package-target-invalidation.ts +173 -0
- package/src/db/005-schema-structure.ts +201 -0
- package/src/db/006-relative-symbol-resolution.ts +443 -0
- package/src/db/007-package-fact-semantics.ts +573 -0
- package/src/db/008-relative-fact-semantics.ts +207 -0
- package/src/db/009-binding-fact-semantics.ts +347 -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 +264 -0
- package/src/db/migrations.ts +16 -3
- package/src/db/repositories.ts +113 -6
- package/src/db/schema.ts +4 -2
- package/src/index.ts +12 -0
- package/src/indexer/repository-indexer.ts +72 -11
- package/src/indexer/workspace-indexer.ts +123 -32
- package/src/linker/003-package-import-symbol-resolver.ts +363 -131
- package/src/linker/004-event-subscription-handler-linker.ts +2 -0
- package/src/linker/005-odata-path-structure.ts +371 -0
- package/src/linker/cross-repo-linker.ts +2 -1
- package/src/linker/odata-path-normalizer.ts +273 -180
- package/src/linker/service-resolver.ts +197 -77
- 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 +343 -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 +240 -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 +152 -0
- package/src/parsers/operation-path-analysis.ts +6 -1
- package/src/parsers/outbound-call-parser.ts +19 -6
- 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 +482 -353
- package/src/trace/002-trace-diagnostics.ts +36 -1
- package/src/trace/016-compact-projector.ts +16 -17
- package/src/trace/020-compact-field-projection.ts +48 -37
- package/src/trace/021-compact-decision-normalization.ts +105 -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 +76 -0
- package/src/trace/025-trace-implementation-scope.ts +123 -0
- package/src/trace/026-trace-start-scope.ts +335 -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/trace-engine.ts +122 -624
- package/src/types.ts +56 -0
- package/src/utils/001-placeholders.ts +188 -10
- package/src/version.ts +1 -1
- package/dist/chunk-ZQABU7MR.js.map +0 -1
|
@@ -7,7 +7,7 @@ export function loadTraceDiagnostics(
|
|
|
7
7
|
workspaceId?: number,
|
|
8
8
|
): Array<Record<string, unknown>> {
|
|
9
9
|
if (repoId === undefined && !includeWorkspaceDiagnostics) return [];
|
|
10
|
-
|
|
10
|
+
const diagnostics = db.prepare(`SELECT d.repo_id repoId,d.severity,d.code,d.message,
|
|
11
11
|
d.source_file sourceFile,d.source_line sourceLine
|
|
12
12
|
FROM diagnostics d LEFT JOIN repositories r ON r.id=d.repo_id
|
|
13
13
|
WHERE (? IS NULL OR d.repo_id=?)
|
|
@@ -18,6 +18,41 @@ export function loadTraceDiagnostics(
|
|
|
18
18
|
) as Array<
|
|
19
19
|
Record<string, unknown>
|
|
20
20
|
>;
|
|
21
|
+
const pending = packagePendingCount(
|
|
22
|
+
db, repoId, includeWorkspaceDiagnostics, workspaceId,
|
|
23
|
+
);
|
|
24
|
+
if (pending > 0) diagnostics.unshift(packagePendingDiagnostic(pending));
|
|
25
|
+
return diagnostics;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function packagePendingCount(
|
|
29
|
+
db: Db,
|
|
30
|
+
repoId: number | undefined,
|
|
31
|
+
includeWorkspace: boolean,
|
|
32
|
+
workspaceId: number | undefined,
|
|
33
|
+
): number {
|
|
34
|
+
const row = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc
|
|
35
|
+
JOIN repositories r ON r.id=sc.repo_id
|
|
36
|
+
WHERE sc.status='unresolved' AND sc.callee_symbol_id IS NULL
|
|
37
|
+
AND sc.unresolved_reason='package_resolution_pending'
|
|
38
|
+
AND (?=1 OR sc.repo_id=?)
|
|
39
|
+
AND (? IS NULL OR r.workspace_id=?)`).get(
|
|
40
|
+
includeWorkspace ? 1 : 0, repoId, workspaceId, workspaceId,
|
|
41
|
+
);
|
|
42
|
+
return Number(row?.count ?? 0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function packagePendingDiagnostic(count: number): Record<string, unknown> {
|
|
46
|
+
return {
|
|
47
|
+
severity: 'warning',
|
|
48
|
+
code: 'package_import_resolution_pending',
|
|
49
|
+
message: 'Package-import facts await workspace linking.',
|
|
50
|
+
packageResolutionState: 'pre_link_pending',
|
|
51
|
+
pendingPackageImportCount: count,
|
|
52
|
+
graphState: 'stale',
|
|
53
|
+
requiredAction: 'relink',
|
|
54
|
+
remediation: 'Run service-flow link --force for this workspace.',
|
|
55
|
+
};
|
|
21
56
|
}
|
|
22
57
|
|
|
23
58
|
export function prependTraceDiagnostic(
|
|
@@ -20,10 +20,13 @@ import {
|
|
|
20
20
|
} from './014-compact-contract.js';
|
|
21
21
|
import {
|
|
22
22
|
compactCompleteness, compactSafeCode, compactStatusCounts, compactStatusTotal,
|
|
23
|
-
|
|
23
|
+
projectCompactDecisionTarget, projectCompactDiagnostics,
|
|
24
24
|
projectCompactQuery, projectCompactStart,
|
|
25
|
-
removeEquivalentCompactPersistedDecision,
|
|
26
25
|
} from './020-compact-field-projection.js';
|
|
26
|
+
import {
|
|
27
|
+
projectObservationDecision,
|
|
28
|
+
type CompactDecisionNode,
|
|
29
|
+
} from './024-compact-observation-decision.js';
|
|
27
30
|
|
|
28
31
|
const REFERENCE_LIMIT = 5;
|
|
29
32
|
|
|
@@ -92,29 +95,25 @@ function observationDecision(
|
|
|
92
95
|
input: CompactEdgeObservation,
|
|
93
96
|
target: ResolvedNode,
|
|
94
97
|
): CompactDecisionV1 {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (decision.persistedResolutionStatus && input.decision?.persistedTarget) {
|
|
99
|
-
const persisted = persistedDecisionTarget(db, input.decision.persistedTarget);
|
|
100
|
-
if (persisted) decision.persistedTarget = persisted;
|
|
101
|
-
}
|
|
102
|
-
removeEquivalentCompactPersistedDecision(decision);
|
|
103
|
-
return decision;
|
|
98
|
+
return projectObservationDecision(
|
|
99
|
+
input, target, (value) => decisionInputNode(db, value),
|
|
100
|
+
);
|
|
104
101
|
}
|
|
105
102
|
|
|
106
|
-
function
|
|
103
|
+
function decisionInputNode(
|
|
107
104
|
db: Db,
|
|
108
105
|
target: CompactDecisionTargetInput,
|
|
109
|
-
):
|
|
106
|
+
): CompactDecisionNode {
|
|
110
107
|
const numeric = numericId(target.id);
|
|
111
108
|
if (target.kind === 'operation' && numeric !== undefined)
|
|
112
|
-
return operationNode(db, numeric)
|
|
109
|
+
return operationNode(db, numeric) ?? {};
|
|
113
110
|
if (target.kind === 'symbol' && numeric !== undefined)
|
|
114
|
-
return symbolNode(db, numeric)
|
|
111
|
+
return symbolNode(db, numeric) ?? {};
|
|
115
112
|
if (target.kind === 'handler_method' && numeric !== undefined)
|
|
116
|
-
return handlerNode(db, numeric)
|
|
117
|
-
return
|
|
113
|
+
return handlerNode(db, numeric) ?? {};
|
|
114
|
+
return {
|
|
115
|
+
decisionTarget: projectCompactDecisionTarget(target.kind, target.id),
|
|
116
|
+
};
|
|
118
117
|
}
|
|
119
118
|
|
|
120
119
|
function resolveEndpoint(
|
|
@@ -17,8 +17,12 @@ import type {
|
|
|
17
17
|
CompactStartV1,
|
|
18
18
|
CompactStatusCountsV1,
|
|
19
19
|
} from './014-compact-contract.js';
|
|
20
|
+
import {
|
|
21
|
+
compactMissingRemediation,
|
|
22
|
+
projectCompactMissingNames,
|
|
23
|
+
type CompactMissingNameProjection,
|
|
24
|
+
} from './021-compact-decision-normalization.js';
|
|
20
25
|
|
|
21
|
-
const compactNameLimit = 8;
|
|
22
26
|
const compactDiagnosticMessages: Readonly<Record<string, string>> = {
|
|
23
27
|
schema_upgrade_required: 'The database schema must be upgraded before tracing.',
|
|
24
28
|
reindex_required: 'Current analyzer facts are required before tracing.',
|
|
@@ -56,14 +60,10 @@ function resolutionDecision(input: CompactDecisionInput): CompactDecisionV1 {
|
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
function addNameDecision(out: CompactDecisionV1, input: CompactDecisionInput): void {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (total === 0) return;
|
|
64
|
-
out.missingVariableCount = total;
|
|
65
|
-
out.shownMissingVariableCount = names.length;
|
|
66
|
-
out.omittedMissingVariableCount = Math.max(0, total - names.length);
|
|
63
|
+
const projection = projectCompactMissingNames(
|
|
64
|
+
input.missingVariableNames, input.missingVariableCount,
|
|
65
|
+
);
|
|
66
|
+
applyMissingProjection(out, projection);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function addDynamicDecision(out: CompactDecisionV1, input: CompactDecisionInput): void {
|
|
@@ -117,14 +117,24 @@ function addRemediationDecision(
|
|
|
117
117
|
out: CompactDecisionV1,
|
|
118
118
|
input: CompactDecisionInput,
|
|
119
119
|
): void {
|
|
120
|
-
const hint = input
|
|
121
|
-
? compactRemediationHint(input.remediationCode) : undefined;
|
|
120
|
+
const hint = decisionRemediation(input);
|
|
122
121
|
if (!hint) return;
|
|
123
122
|
out.remediationHint = hint;
|
|
124
123
|
const total = Math.max(1, compactCount(input.remediationHintCount));
|
|
125
124
|
out.omittedRemediationHintCount = Math.max(0, total - 1);
|
|
126
125
|
}
|
|
127
126
|
|
|
127
|
+
function decisionRemediation(input: CompactDecisionInput): string | undefined {
|
|
128
|
+
if (input.remediationCode === 'provide_runtime_variables') {
|
|
129
|
+
const projection = projectCompactMissingNames(
|
|
130
|
+
input.missingVariableNames, input.missingVariableCount,
|
|
131
|
+
);
|
|
132
|
+
return compactMissingRemediation(projection, 'detailed trace edge');
|
|
133
|
+
}
|
|
134
|
+
return input.remediationCode
|
|
135
|
+
? compactRemediationHint(input.remediationCode) : undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
128
138
|
export function projectCompactDiagnostics(
|
|
129
139
|
values: Array<Record<string, unknown>>,
|
|
130
140
|
): CompactProjectedDiagnostic[] {
|
|
@@ -200,15 +210,6 @@ export function compactStatusTotal(counts: CompactStatusCountsV1): number {
|
|
|
200
210
|
+ counts.ambiguous + counts.unresolved + counts.cycle;
|
|
201
211
|
}
|
|
202
212
|
|
|
203
|
-
export function removeEquivalentCompactPersistedDecision(
|
|
204
|
-
decision: CompactDecisionV1,
|
|
205
|
-
): void {
|
|
206
|
-
if (decision.persistedResolutionStatus !== decision.effectiveResolutionStatus) return;
|
|
207
|
-
if (!decision.persistedTarget || decision.persistedTarget !== decision.effectiveTarget) return;
|
|
208
|
-
delete decision.persistedResolutionStatus;
|
|
209
|
-
delete decision.persistedTarget;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
213
|
function compactBlockingDiagnostic(item: CompactDiagnosticRowV1): boolean {
|
|
213
214
|
if (item[1] === 'error') return true;
|
|
214
215
|
return item[2] === 'schema_upgrade_required'
|
|
@@ -242,9 +243,9 @@ function compactDiagnosticDetails(
|
|
|
242
243
|
const out: CompactDiagnosticDetailsV1 = {};
|
|
243
244
|
const reasonCode = compactSafeCode(value.reasonCode);
|
|
244
245
|
if (reasonCode) out.reasonCode = reasonCode;
|
|
245
|
-
addDiagnosticNames(out, value);
|
|
246
|
+
if (code === 'trace_runtime_variables_missing') addDiagnosticNames(out, value);
|
|
246
247
|
addDiagnosticCounts(out, value);
|
|
247
|
-
const hint = compactDiagnosticRemediation(code);
|
|
248
|
+
const hint = compactDiagnosticRemediation(code, out);
|
|
248
249
|
if (hint) {
|
|
249
250
|
out.remediationHint = hint;
|
|
250
251
|
out.omittedHintCount = Math.max(
|
|
@@ -258,14 +259,21 @@ function addDiagnosticNames(
|
|
|
258
259
|
out: CompactDiagnosticDetailsV1,
|
|
259
260
|
value: Record<string, unknown>,
|
|
260
261
|
): void {
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
out
|
|
262
|
+
const projection = projectCompactMissingNames(
|
|
263
|
+
compactStringArray(value.missingVariables), value.missingVariableCount,
|
|
264
|
+
);
|
|
265
|
+
applyMissingProjection(out, projection);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function applyMissingProjection(
|
|
269
|
+
out: CompactDecisionV1 | CompactDiagnosticDetailsV1,
|
|
270
|
+
projection: CompactMissingNameProjection,
|
|
271
|
+
): void {
|
|
272
|
+
if (projection.names.length > 0) out.missingVariableNames = projection.names;
|
|
273
|
+
if (projection.total === 0) return;
|
|
274
|
+
out.missingVariableCount = projection.total;
|
|
275
|
+
out.shownMissingVariableCount = projection.shown;
|
|
276
|
+
out.omittedMissingVariableCount = projection.omitted;
|
|
269
277
|
}
|
|
270
278
|
|
|
271
279
|
function addDiagnosticCounts(
|
|
@@ -303,11 +311,19 @@ function compactDiagnosticSeverity(value: unknown): 'error' | 'warning' | 'info'
|
|
|
303
311
|
return value === 'error' || value === 'warning' ? value : 'info';
|
|
304
312
|
}
|
|
305
313
|
|
|
306
|
-
function compactDiagnosticRemediation(
|
|
314
|
+
function compactDiagnosticRemediation(
|
|
315
|
+
code: string,
|
|
316
|
+
details: CompactDiagnosticDetailsV1,
|
|
317
|
+
): string | undefined {
|
|
307
318
|
if (code === 'schema_upgrade_required' || code === 'reindex_required')
|
|
308
319
|
return compactRemediationHint('reindex_and_link');
|
|
309
320
|
if (code === 'trace_runtime_variables_missing')
|
|
310
|
-
return
|
|
321
|
+
return compactMissingRemediation({
|
|
322
|
+
names: details.missingVariableNames ?? [],
|
|
323
|
+
total: details.missingVariableCount ?? 0,
|
|
324
|
+
shown: details.shownMissingVariableCount ?? 0,
|
|
325
|
+
omitted: details.omittedMissingVariableCount ?? 0,
|
|
326
|
+
}, 'detailed diagnostic');
|
|
311
327
|
if (code === 'implementation_hint_mismatch')
|
|
312
328
|
return compactRemediationHint('select_implementation');
|
|
313
329
|
return undefined;
|
|
@@ -358,11 +374,6 @@ function compactSafeSourceFile(value: unknown): string | undefined {
|
|
|
358
374
|
&& !/^[a-z]+:\/\//i.test(value) && !/[\r\n]/.test(value) ? value : undefined;
|
|
359
375
|
}
|
|
360
376
|
|
|
361
|
-
function safeVariableNames(values: string[] | undefined): string[] {
|
|
362
|
-
return compactSortedUnique((values ?? [])
|
|
363
|
-
.filter((value) => /^[A-Za-z_$][\w$]*$/.test(value)));
|
|
364
|
-
}
|
|
365
|
-
|
|
366
377
|
function compactCount(value: unknown): number {
|
|
367
378
|
return typeof value === 'number' && Number.isFinite(value)
|
|
368
379
|
? Math.max(0, Math.floor(value)) : 0;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { compareBinary } from './010-traversal-scope.js';
|
|
2
|
+
import type {
|
|
3
|
+
CompactDecisionV1,
|
|
4
|
+
CompactStatus,
|
|
5
|
+
} from './014-compact-contract.js';
|
|
6
|
+
|
|
7
|
+
export const COMPACT_MISSING_NAME_LIMIT = 8;
|
|
8
|
+
export const COMPACT_MISSING_NAME_MAX_LENGTH = 160;
|
|
9
|
+
|
|
10
|
+
export interface CompactMissingNameProjection {
|
|
11
|
+
readonly names: string[];
|
|
12
|
+
readonly total: number;
|
|
13
|
+
readonly shown: number;
|
|
14
|
+
readonly omitted: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const identifier = '[A-Za-z_$][A-Za-z0-9_$]*';
|
|
18
|
+
const member = `(?:(?:\\.|\\?\\.)${identifier}|(?:\\[|\\?\\.\\[)(?:0|[1-9][0-9]*)\\])`;
|
|
19
|
+
const transform = '(?:(?:\\.|\\?\\.)(?:toLowerCase|toUpperCase|trim)\\(\\))?';
|
|
20
|
+
const safeMissingName = new RegExp(`^${identifier}(?:${member})*${transform}$`);
|
|
21
|
+
|
|
22
|
+
export function projectCompactMissingNames(
|
|
23
|
+
values: readonly string[] | undefined,
|
|
24
|
+
authoritativeCount: unknown,
|
|
25
|
+
): CompactMissingNameProjection {
|
|
26
|
+
const raw = [...new Set((values ?? [])
|
|
27
|
+
.map((value) => value.trim())
|
|
28
|
+
.filter(Boolean))]
|
|
29
|
+
.sort(compareBinary);
|
|
30
|
+
const safe = raw.filter(isSafeCompactMissingName);
|
|
31
|
+
const names = safe.slice(0, COMPACT_MISSING_NAME_LIMIT);
|
|
32
|
+
const total = Math.max(compactCount(authoritativeCount), raw.length);
|
|
33
|
+
return {
|
|
34
|
+
names, total, shown: names.length,
|
|
35
|
+
omitted: Math.max(0, total - names.length),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isSafeCompactMissingName(value: string): boolean {
|
|
40
|
+
const normalized = value.trim();
|
|
41
|
+
return normalized.length > 0
|
|
42
|
+
&& normalized.length <= COMPACT_MISSING_NAME_MAX_LENGTH
|
|
43
|
+
&& !hasControlCharacter(normalized)
|
|
44
|
+
&& safeMissingName.test(normalized);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function normalizeCompactDecisionEquivalence(
|
|
48
|
+
decision: CompactDecisionV1,
|
|
49
|
+
status: CompactStatus,
|
|
50
|
+
canonicalTarget: string | undefined,
|
|
51
|
+
targetIdentityEqual = false,
|
|
52
|
+
persistedIdentityEqual = false,
|
|
53
|
+
): void {
|
|
54
|
+
removeEquivalentPersistedDecision(
|
|
55
|
+
decision, status, canonicalTarget, persistedIdentityEqual,
|
|
56
|
+
);
|
|
57
|
+
if (decision.effectiveResolutionStatus === status)
|
|
58
|
+
delete decision.effectiveResolutionStatus;
|
|
59
|
+
if (targetIdentityEqual && canonicalTarget !== undefined
|
|
60
|
+
&& decision.effectiveTarget === canonicalTarget)
|
|
61
|
+
delete decision.effectiveTarget;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function compactMissingRemediation(
|
|
65
|
+
projection: CompactMissingNameProjection,
|
|
66
|
+
artifact: 'detailed diagnostic' | 'detailed trace edge',
|
|
67
|
+
): string {
|
|
68
|
+
if (projection.total > 0 && projection.shown === projection.total)
|
|
69
|
+
return 'Provide the missing variable names listed in details.';
|
|
70
|
+
if (projection.shown > 0)
|
|
71
|
+
return `Provide the listed names; inspect the correlated ${artifact} for omitted names.`;
|
|
72
|
+
return `Inspect the correlated ${artifact} for missing variable names.`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function removeEquivalentPersistedDecision(
|
|
76
|
+
decision: CompactDecisionV1,
|
|
77
|
+
status: CompactStatus,
|
|
78
|
+
canonicalTarget: string | undefined,
|
|
79
|
+
identityEqual: boolean,
|
|
80
|
+
): void {
|
|
81
|
+
if (!identityEqual
|
|
82
|
+
|| decision.persistedResolutionStatus !== status
|
|
83
|
+
|| (decision.effectiveResolutionStatus !== undefined
|
|
84
|
+
&& decision.persistedResolutionStatus
|
|
85
|
+
!== decision.effectiveResolutionStatus))
|
|
86
|
+
return;
|
|
87
|
+
if (!decision.persistedTarget || canonicalTarget === undefined
|
|
88
|
+
|| decision.persistedTarget !== canonicalTarget)
|
|
89
|
+
return;
|
|
90
|
+
delete decision.persistedResolutionStatus;
|
|
91
|
+
delete decision.persistedTarget;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function compactCount(value: unknown): number {
|
|
95
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
96
|
+
? Math.max(0, Math.floor(value)) : 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function hasControlCharacter(value: string): boolean {
|
|
100
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
101
|
+
const code = value.charCodeAt(index);
|
|
102
|
+
if (code <= 31 || code === 127) return true;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Db } from '../db/connection.js';
|
|
2
|
+
import { currentFactLifecycleDiagnostic } from '../db/001-fact-lifecycle.js';
|
|
3
|
+
import { resolveTraversalWorkspaceId } from './010-traversal-scope.js';
|
|
4
|
+
|
|
5
|
+
function workspaceScopeIsAmbiguous(db: Db): boolean {
|
|
6
|
+
const rows = db.prepare(`SELECT DISTINCT workspace_id workspaceId
|
|
7
|
+
FROM repositories ORDER BY workspace_id LIMIT 2`).all();
|
|
8
|
+
return rows.length > 1;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function shouldDeferTraceStartSelection(
|
|
12
|
+
db: Db,
|
|
13
|
+
requestedWorkspaceId: number | undefined,
|
|
14
|
+
repoId: number | undefined,
|
|
15
|
+
): boolean {
|
|
16
|
+
const workspaceId = resolveTraversalWorkspaceId(
|
|
17
|
+
db, requestedWorkspaceId, repoId,
|
|
18
|
+
);
|
|
19
|
+
if (workspaceId === undefined) return workspaceScopeIsAmbiguous(db);
|
|
20
|
+
return currentFactLifecycleDiagnostic(db, workspaceId) !== undefined;
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Db } from '../db/connection.js';
|
|
2
|
+
|
|
3
|
+
export function outboundScopeSymbolIds(
|
|
4
|
+
db: Db,
|
|
5
|
+
symbolIds: ReadonlySet<number> | undefined,
|
|
6
|
+
includeAsync: boolean,
|
|
7
|
+
): ReadonlySet<number> | undefined {
|
|
8
|
+
if (!includeAsync || !symbolIds || symbolIds.size === 0) return symbolIds;
|
|
9
|
+
const ids = [...symbolIds];
|
|
10
|
+
const rows = db.prepare(`SELECT DISTINCT child.id FROM symbols parent
|
|
11
|
+
JOIN symbols child ON child.repo_id=parent.repo_id
|
|
12
|
+
AND child.source_file=parent.source_file
|
|
13
|
+
AND child.kind='event_registration'
|
|
14
|
+
AND parent.start_offset<=child.start_offset
|
|
15
|
+
AND parent.end_offset>=child.end_offset
|
|
16
|
+
WHERE parent.id IN (${ids.map(() => '?').join(',')})
|
|
17
|
+
ORDER BY child.id`).all(...ids);
|
|
18
|
+
return new Set([
|
|
19
|
+
...ids,
|
|
20
|
+
...rows.flatMap((row) =>
|
|
21
|
+
typeof row.id === 'number' ? [row.id] : []),
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CompactDecisionTargetInput,
|
|
3
|
+
CompactDecisionV1,
|
|
4
|
+
CompactEdgeObservation,
|
|
5
|
+
} from './014-compact-contract.js';
|
|
6
|
+
import { projectCompactDecision } from './020-compact-field-projection.js';
|
|
7
|
+
import { normalizeCompactDecisionEquivalence } from
|
|
8
|
+
'./021-compact-decision-normalization.js';
|
|
9
|
+
|
|
10
|
+
export interface CompactDecisionNode {
|
|
11
|
+
key?: string;
|
|
12
|
+
decisionTarget?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolvedNode(
|
|
16
|
+
value: CompactDecisionTargetInput | undefined,
|
|
17
|
+
resolve: (input: CompactDecisionTargetInput) => CompactDecisionNode,
|
|
18
|
+
): CompactDecisionNode | undefined {
|
|
19
|
+
return value ? resolve(value) : undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function applyEffectiveTarget(
|
|
23
|
+
decision: CompactDecisionV1,
|
|
24
|
+
effective: CompactDecisionNode | undefined,
|
|
25
|
+
): void {
|
|
26
|
+
if (effective?.decisionTarget)
|
|
27
|
+
decision.effectiveTarget = effective.decisionTarget;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function applyPersistedTarget(
|
|
31
|
+
decision: CompactDecisionV1,
|
|
32
|
+
persisted: CompactDecisionNode | undefined,
|
|
33
|
+
status: string | undefined,
|
|
34
|
+
): void {
|
|
35
|
+
if (persisted?.decisionTarget && status)
|
|
36
|
+
decision.persistedTarget = persisted.decisionTarget;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function sameCanonicalNode(
|
|
40
|
+
left: CompactDecisionNode | undefined,
|
|
41
|
+
right: CompactDecisionNode,
|
|
42
|
+
): boolean {
|
|
43
|
+
return left?.key !== undefined && left.key === right.key;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function persistedCanonical(
|
|
47
|
+
persisted: CompactDecisionNode | undefined,
|
|
48
|
+
effective: CompactDecisionNode | undefined,
|
|
49
|
+
target: CompactDecisionNode,
|
|
50
|
+
): boolean {
|
|
51
|
+
if (!sameCanonicalNode(persisted, target)) return false;
|
|
52
|
+
return effective?.key === undefined || effective.key === target.key;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function projectObservationDecision(
|
|
56
|
+
input: CompactEdgeObservation,
|
|
57
|
+
target: CompactDecisionNode,
|
|
58
|
+
resolve: (value: CompactDecisionTargetInput) => CompactDecisionNode,
|
|
59
|
+
): CompactDecisionV1 {
|
|
60
|
+
const decision = projectCompactDecision(input.decision);
|
|
61
|
+
const source = input.decision;
|
|
62
|
+
const effective = resolvedNode(source?.effectiveTarget, resolve);
|
|
63
|
+
const persisted = resolvedNode(source?.persistedTarget, resolve);
|
|
64
|
+
applyEffectiveTarget(decision, effective);
|
|
65
|
+
applyPersistedTarget(
|
|
66
|
+
decision, persisted, source?.persistedResolutionStatus,
|
|
67
|
+
);
|
|
68
|
+
normalizeCompactDecisionEquivalence(
|
|
69
|
+
decision,
|
|
70
|
+
input.status,
|
|
71
|
+
target.decisionTarget,
|
|
72
|
+
sameCanonicalNode(effective, target),
|
|
73
|
+
persistedCanonical(persisted, effective, target),
|
|
74
|
+
);
|
|
75
|
+
return decision;
|
|
76
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Db } from '../db/connection.js';
|
|
2
|
+
import type { ImplementationHint } from '../types.js';
|
|
3
|
+
import type { TraceGraphEdgeRow } from './012-trace-graph-lookups.js';
|
|
4
|
+
|
|
5
|
+
export interface ImplementationHintOptions {
|
|
6
|
+
implementationRepo?: string;
|
|
7
|
+
implementationHints?: ImplementationHint[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ImplementationScope {
|
|
11
|
+
repoId?: number;
|
|
12
|
+
files: Set<string>;
|
|
13
|
+
symbolId?: number;
|
|
14
|
+
edge?: TraceGraphEdgeRow;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface HandlerScopeRow {
|
|
18
|
+
repoId?: number;
|
|
19
|
+
sourceFile?: string;
|
|
20
|
+
symbolId?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeOperation(value: string | undefined): string | undefined {
|
|
24
|
+
if (!value) return undefined;
|
|
25
|
+
return value.startsWith('/') ? value.slice(1) : value;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function implementationEdge(
|
|
29
|
+
db: Db,
|
|
30
|
+
operationId: string,
|
|
31
|
+
): TraceGraphEdgeRow | undefined {
|
|
32
|
+
return db.prepare(`SELECT * FROM graph_edges
|
|
33
|
+
WHERE edge_type='OPERATION_IMPLEMENTED_BY_HANDLER'
|
|
34
|
+
AND from_kind='operation' AND from_id=?
|
|
35
|
+
ORDER BY CASE status WHEN 'resolved' THEN 0
|
|
36
|
+
WHEN 'ambiguous' THEN 1 ELSE 2 END,id LIMIT 1`)
|
|
37
|
+
.get(operationId) as TraceGraphEdgeRow | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handlerScopeRow(db: Db, methodId: string): HandlerScopeRow | undefined {
|
|
41
|
+
return db.prepare(`SELECT hc.repo_id repoId,hc.source_file sourceFile,
|
|
42
|
+
s.id symbolId
|
|
43
|
+
FROM handler_methods hm
|
|
44
|
+
JOIN handler_classes hc ON hc.id=hm.handler_class_id
|
|
45
|
+
LEFT JOIN symbols s ON s.repo_id=hc.repo_id
|
|
46
|
+
AND s.source_file=hc.source_file
|
|
47
|
+
AND s.qualified_name=hc.class_name || '.' || hm.method_name
|
|
48
|
+
AND s.start_line=hm.source_line
|
|
49
|
+
WHERE hm.id=?`).get(methodId) as HandlerScopeRow | undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function implementationScope(
|
|
53
|
+
db: Db,
|
|
54
|
+
operationId: string,
|
|
55
|
+
): ImplementationScope {
|
|
56
|
+
const edge = implementationEdge(db, operationId);
|
|
57
|
+
if (!edge || edge.status !== 'resolved') return { files: new Set(), edge };
|
|
58
|
+
const row = handlerScopeRow(db, edge.to_id);
|
|
59
|
+
if (!row || typeof row.symbolId !== 'number')
|
|
60
|
+
return { repoId: row?.repoId, files: new Set(), edge };
|
|
61
|
+
return {
|
|
62
|
+
repoId: row.repoId,
|
|
63
|
+
files: new Set(row.sourceFile ? [row.sourceFile] : []),
|
|
64
|
+
symbolId: row.symbolId,
|
|
65
|
+
edge,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function handlerScope(
|
|
70
|
+
db: Db,
|
|
71
|
+
methodId: string,
|
|
72
|
+
): Omit<ImplementationScope, 'edge'> | undefined {
|
|
73
|
+
const row = handlerScopeRow(db, methodId);
|
|
74
|
+
if (!row || typeof row.symbolId !== 'number') return undefined;
|
|
75
|
+
return {
|
|
76
|
+
repoId: row.repoId,
|
|
77
|
+
files: new Set(row.sourceFile ? [row.sourceFile] : []),
|
|
78
|
+
symbolId: row.symbolId,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function handlerFilesForOperation(
|
|
83
|
+
db: Db,
|
|
84
|
+
operationId: string,
|
|
85
|
+
): Set<string> {
|
|
86
|
+
const op = db.prepare(`SELECT o.operation_name operationName,
|
|
87
|
+
o.operation_path operationPath,s.repo_id repoId
|
|
88
|
+
FROM cds_operations o JOIN cds_services s ON s.id=o.service_id
|
|
89
|
+
WHERE o.id=?`).get(operationId) as {
|
|
90
|
+
operationName?: string;
|
|
91
|
+
operationPath?: string;
|
|
92
|
+
repoId?: number;
|
|
93
|
+
} | undefined;
|
|
94
|
+
if (!op) return new Set();
|
|
95
|
+
const operation = normalizeOperation(op.operationPath ?? op.operationName);
|
|
96
|
+
const rows = handlerFileRows(db, op.repoId, operation, op.operationName);
|
|
97
|
+
return new Set(rows.flatMap((row) =>
|
|
98
|
+
typeof row.sourceFile === 'string' ? [row.sourceFile] : []));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function handlerFileRows(
|
|
102
|
+
db: Db,
|
|
103
|
+
repoId: number | undefined,
|
|
104
|
+
operation: string | undefined,
|
|
105
|
+
operationName: string | undefined,
|
|
106
|
+
): Array<{ sourceFile?: string }> {
|
|
107
|
+
return db.prepare(`SELECT DISTINCT hc.source_file sourceFile
|
|
108
|
+
FROM handler_classes hc
|
|
109
|
+
JOIN handler_methods hm ON hm.handler_class_id=hc.id
|
|
110
|
+
WHERE hc.repo_id=?
|
|
111
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.handlerKind'),
|
|
112
|
+
CASE WHEN hm.decorator_kind='Event' THEN 'event'
|
|
113
|
+
WHEN hm.decorator_kind IN ('Action','Func','On') THEN 'operation'
|
|
114
|
+
ELSE 'unsupported' END)='operation'
|
|
115
|
+
AND COALESCE(json_extract(hm.decorator_resolution_json,'$.executable'),
|
|
116
|
+
CASE WHEN hm.decorator_kind IN ('Action','Func','On')
|
|
117
|
+
THEN 1 ELSE 0 END)=1
|
|
118
|
+
AND (hm.decorator_value=? OR hm.method_name=?
|
|
119
|
+
OR hm.decorator_value=?)`)
|
|
120
|
+
.all(repoId, operation, operation, operationName) as Array<{
|
|
121
|
+
sourceFile?: string;
|
|
122
|
+
}>;
|
|
123
|
+
}
|