@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,268 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LexicalScopeFact,
|
|
3
|
+
ServiceBindingReference,
|
|
4
|
+
} from '../types.js';
|
|
5
|
+
|
|
6
|
+
export interface BindingProofCall {
|
|
7
|
+
repoId: number;
|
|
8
|
+
bindingId: number | null;
|
|
9
|
+
variableName?: string;
|
|
10
|
+
sourceFile: string;
|
|
11
|
+
startOffset: number;
|
|
12
|
+
endOffset: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface BindingProofTarget {
|
|
16
|
+
id: number;
|
|
17
|
+
repoId: number;
|
|
18
|
+
symbolId: number | null;
|
|
19
|
+
variableName: string;
|
|
20
|
+
sourceFile: string;
|
|
21
|
+
startOffset: number;
|
|
22
|
+
endOffset: number;
|
|
23
|
+
ownerResolution: string;
|
|
24
|
+
ownerStartOffset: number | null;
|
|
25
|
+
ownerEndOffset: number | null;
|
|
26
|
+
singleHopHelperReturn: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ResolvedBindingProof {
|
|
30
|
+
variableName: string;
|
|
31
|
+
bindingSourceFile: string;
|
|
32
|
+
bindingSiteStartOffset: number;
|
|
33
|
+
bindingSiteEndOffset: number;
|
|
34
|
+
resolutionStrategy: NonNullable<
|
|
35
|
+
ServiceBindingReference['resolutionStrategy']
|
|
36
|
+
>;
|
|
37
|
+
lexicalScopeChain: LexicalScopeFact[];
|
|
38
|
+
bindingScopeIndex: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const scopeKinds = new Set([
|
|
42
|
+
'source_file', 'module_block', 'function', 'class', 'loop',
|
|
43
|
+
'case_block', 'block', 'catch',
|
|
44
|
+
]);
|
|
45
|
+
const strategies = new Set([
|
|
46
|
+
'lexical_declaration',
|
|
47
|
+
'lexical_alias_declaration',
|
|
48
|
+
'deterministic_reaching_assignment',
|
|
49
|
+
'single_hop_helper_return',
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
53
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
54
|
+
? value as Record<string, unknown>
|
|
55
|
+
: undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function integer(value: unknown): value is number {
|
|
59
|
+
return Number.isInteger(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function nonEmpty(value: unknown): value is string {
|
|
63
|
+
return typeof value === 'string' && value.length > 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function all(values: readonly boolean[]): boolean {
|
|
67
|
+
return values.every(Boolean);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function validBindingLexicalScope(
|
|
71
|
+
value: unknown,
|
|
72
|
+
): value is LexicalScopeFact {
|
|
73
|
+
const scope = record(value);
|
|
74
|
+
return Boolean(scope && scopeKinds.has(String(scope.kind))
|
|
75
|
+
&& integer(scope.startOffset) && integer(scope.endOffset)
|
|
76
|
+
&& Number(scope.startOffset) >= 0
|
|
77
|
+
&& Number(scope.endOffset) > Number(scope.startOffset));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function bindingReferenceCountsValid(
|
|
81
|
+
reference: Record<string, unknown>,
|
|
82
|
+
): boolean {
|
|
83
|
+
const total = reference.scopeChainTotal;
|
|
84
|
+
const shown = reference.scopeChainShown;
|
|
85
|
+
const omitted = reference.scopeChainOmitted;
|
|
86
|
+
return integer(total) && integer(shown) && integer(omitted)
|
|
87
|
+
&& total >= 0 && shown >= 0 && omitted >= 0 && shown <= 16
|
|
88
|
+
&& shown + omitted === total;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function scopeArray(value: unknown): LexicalScopeFact[] | undefined {
|
|
92
|
+
if (!Array.isArray(value) || !value.every(validBindingLexicalScope))
|
|
93
|
+
return undefined;
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolvedProofFields(
|
|
98
|
+
call: BindingProofCall,
|
|
99
|
+
reference: Record<string, unknown>,
|
|
100
|
+
): boolean {
|
|
101
|
+
return all([
|
|
102
|
+
reference.status === 'resolved_exact',
|
|
103
|
+
nonEmpty(reference.variableName),
|
|
104
|
+
call.variableName === undefined
|
|
105
|
+
|| reference.variableName === call.variableName,
|
|
106
|
+
reference.bindingSourceFile === call.sourceFile,
|
|
107
|
+
integer(reference.bindingSiteStartOffset),
|
|
108
|
+
integer(reference.bindingSiteEndOffset),
|
|
109
|
+
strategies.has(String(reference.resolutionStrategy)),
|
|
110
|
+
reference.reason === undefined,
|
|
111
|
+
]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function resolvedProof(
|
|
115
|
+
call: BindingProofCall,
|
|
116
|
+
value: unknown,
|
|
117
|
+
): ResolvedBindingProof | undefined {
|
|
118
|
+
const reference = record(value);
|
|
119
|
+
if (!reference || !resolvedProofFields(call, reference)) return undefined;
|
|
120
|
+
const chain = scopeArray(reference.lexicalScopeChain);
|
|
121
|
+
const index = reference.bindingScopeIndex;
|
|
122
|
+
if (!chain || !integer(index) || !resolvedChainValid(
|
|
123
|
+
reference, chain, index,
|
|
124
|
+
)) return undefined;
|
|
125
|
+
if (!resolvedProofValuesValid(reference)) return undefined;
|
|
126
|
+
return {
|
|
127
|
+
variableName: reference.variableName,
|
|
128
|
+
bindingSourceFile: reference.bindingSourceFile,
|
|
129
|
+
bindingSiteStartOffset: reference.bindingSiteStartOffset,
|
|
130
|
+
bindingSiteEndOffset: reference.bindingSiteEndOffset,
|
|
131
|
+
resolutionStrategy: reference.resolutionStrategy,
|
|
132
|
+
lexicalScopeChain: chain,
|
|
133
|
+
bindingScopeIndex: index,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function resolvedProofValuesValid(
|
|
138
|
+
value: Record<string, unknown>,
|
|
139
|
+
): value is Record<string, unknown> & {
|
|
140
|
+
variableName: string;
|
|
141
|
+
bindingSourceFile: string;
|
|
142
|
+
bindingSiteStartOffset: number;
|
|
143
|
+
bindingSiteEndOffset: number;
|
|
144
|
+
resolutionStrategy: ResolvedBindingProof['resolutionStrategy'];
|
|
145
|
+
} {
|
|
146
|
+
return all([
|
|
147
|
+
nonEmpty(value.variableName),
|
|
148
|
+
nonEmpty(value.bindingSourceFile),
|
|
149
|
+
integer(value.bindingSiteStartOffset),
|
|
150
|
+
integer(value.bindingSiteEndOffset),
|
|
151
|
+
strategies.has(String(value.resolutionStrategy)),
|
|
152
|
+
]);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function resolvedChainValid(
|
|
156
|
+
reference: Record<string, unknown>,
|
|
157
|
+
chain: readonly LexicalScopeFact[],
|
|
158
|
+
index: number,
|
|
159
|
+
): boolean {
|
|
160
|
+
return all([
|
|
161
|
+
index >= 0,
|
|
162
|
+
index < chain.length,
|
|
163
|
+
chain[0]?.kind === 'source_file',
|
|
164
|
+
bindingReferenceCountsValid(reference),
|
|
165
|
+
reference.scopeChainShown === chain.length,
|
|
166
|
+
reference.scopeChainOmitted === 0,
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function contains(
|
|
171
|
+
scope: LexicalScopeFact,
|
|
172
|
+
startOffset: number,
|
|
173
|
+
endOffset: number,
|
|
174
|
+
): boolean {
|
|
175
|
+
return scope.startOffset <= startOffset && scope.endOffset >= endOffset;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function strictlyNested(scopes: readonly LexicalScopeFact[]): boolean {
|
|
179
|
+
return scopes.every((scope, index) => {
|
|
180
|
+
const child = scopes[index + 1];
|
|
181
|
+
return !child || contains(scope, child.startOffset, child.endOffset)
|
|
182
|
+
&& (scope.startOffset < child.startOffset
|
|
183
|
+
|| scope.endOffset > child.endOffset);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function deepestContainingScopeIndex(
|
|
188
|
+
chain: readonly LexicalScopeFact[],
|
|
189
|
+
startOffset: number,
|
|
190
|
+
endOffset: number,
|
|
191
|
+
): number {
|
|
192
|
+
let result = -1;
|
|
193
|
+
chain.forEach((scope, index) => {
|
|
194
|
+
if (contains(scope, startOffset, endOffset)) result = index;
|
|
195
|
+
});
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function scopeProofValid(
|
|
200
|
+
call: BindingProofCall,
|
|
201
|
+
reference: ResolvedBindingProof,
|
|
202
|
+
): boolean {
|
|
203
|
+
const chain = reference.lexicalScopeChain;
|
|
204
|
+
const siteStart = reference.bindingSiteStartOffset;
|
|
205
|
+
const siteEnd = reference.bindingSiteEndOffset;
|
|
206
|
+
const scopeIndex = reference.bindingScopeIndex;
|
|
207
|
+
const deepest = deepestContainingScopeIndex(chain, siteStart, siteEnd);
|
|
208
|
+
const siteScopeValid = reference.resolutionStrategy
|
|
209
|
+
=== 'deterministic_reaching_assignment'
|
|
210
|
+
? deepest >= scopeIndex
|
|
211
|
+
: deepest === scopeIndex;
|
|
212
|
+
return all([
|
|
213
|
+
siteStart >= 0, siteEnd > siteStart, siteEnd <= call.startOffset,
|
|
214
|
+
strictlyNested(chain),
|
|
215
|
+
chain.every((scope) => contains(scope, call.startOffset, call.endOffset)),
|
|
216
|
+
chain.slice(0, scopeIndex + 1).every((scope) =>
|
|
217
|
+
contains(scope, siteStart, siteEnd)),
|
|
218
|
+
siteScopeValid,
|
|
219
|
+
]);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function ownerCompatible(
|
|
223
|
+
call: BindingProofCall,
|
|
224
|
+
target: BindingProofTarget,
|
|
225
|
+
): boolean {
|
|
226
|
+
if (target.ownerResolution === 'ownerless_file_scope')
|
|
227
|
+
return target.symbolId === null
|
|
228
|
+
&& target.ownerStartOffset === null && target.ownerEndOffset === null;
|
|
229
|
+
return all([
|
|
230
|
+
target.ownerResolution === 'owned_exact',
|
|
231
|
+
target.symbolId !== null,
|
|
232
|
+
integer(target.ownerStartOffset),
|
|
233
|
+
integer(target.ownerEndOffset),
|
|
234
|
+
Number(target.ownerStartOffset) <= target.startOffset,
|
|
235
|
+
Number(target.ownerEndOffset) >= target.endOffset,
|
|
236
|
+
Number(target.ownerStartOffset) <= call.startOffset,
|
|
237
|
+
Number(target.ownerEndOffset) >= call.endOffset,
|
|
238
|
+
]);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function targetMatches(
|
|
242
|
+
call: BindingProofCall,
|
|
243
|
+
reference: ResolvedBindingProof,
|
|
244
|
+
target: BindingProofTarget | undefined,
|
|
245
|
+
): boolean {
|
|
246
|
+
if (!target || call.bindingId !== target.id) return false;
|
|
247
|
+
if (reference.resolutionStrategy === 'single_hop_helper_return'
|
|
248
|
+
&& !target.singleHopHelperReturn) return false;
|
|
249
|
+
return all([
|
|
250
|
+
target.repoId === call.repoId,
|
|
251
|
+
target.sourceFile === call.sourceFile,
|
|
252
|
+
target.sourceFile === reference.bindingSourceFile,
|
|
253
|
+
target.variableName === reference.variableName,
|
|
254
|
+
target.startOffset === reference.bindingSiteStartOffset,
|
|
255
|
+
target.endOffset === reference.bindingSiteEndOffset,
|
|
256
|
+
ownerCompatible(call, target),
|
|
257
|
+
]);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function resolvedBindingReferenceProofValid(
|
|
261
|
+
call: BindingProofCall,
|
|
262
|
+
referenceValue: unknown,
|
|
263
|
+
target: BindingProofTarget | undefined,
|
|
264
|
+
): boolean {
|
|
265
|
+
const reference = resolvedProof(call, referenceValue);
|
|
266
|
+
return Boolean(reference && scopeProofValid(call, reference)
|
|
267
|
+
&& targetMatches(call, reference, target));
|
|
268
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Db } from './connection.js';
|
|
2
|
+
|
|
3
|
+
export type PreparedRepositoryFactKind =
|
|
4
|
+
| 'outbound_call'
|
|
5
|
+
| 'service_binding'
|
|
6
|
+
| 'symbol_call';
|
|
7
|
+
|
|
8
|
+
export type PreparedSnapshotFailureCode =
|
|
9
|
+
| 'binding_lexical_proof_invalid'
|
|
10
|
+
| 'binding_owner_mismatch'
|
|
11
|
+
| 'binding_reference_mismatch'
|
|
12
|
+
| 'binding_reference_missing'
|
|
13
|
+
| 'binding_site_missing'
|
|
14
|
+
| 'duplicate_service_binding_site'
|
|
15
|
+
| 'outbound_owner_mismatch'
|
|
16
|
+
| 'package_import_provenance_missing'
|
|
17
|
+
| 'symbol_call_owner_mismatch';
|
|
18
|
+
|
|
19
|
+
export interface PreparedSnapshotFailureSite {
|
|
20
|
+
factKind: PreparedRepositoryFactKind;
|
|
21
|
+
sourceFile?: string;
|
|
22
|
+
sourceLine?: number;
|
|
23
|
+
callSiteStartOffset?: number;
|
|
24
|
+
callSiteEndOffset?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class PreparedRepositorySnapshotError extends Error {
|
|
28
|
+
readonly failureCode: PreparedSnapshotFailureCode;
|
|
29
|
+
readonly site: PreparedSnapshotFailureSite;
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
failureCode: PreparedSnapshotFailureCode,
|
|
33
|
+
site: PreparedSnapshotFailureSite,
|
|
34
|
+
) {
|
|
35
|
+
super(`invalid_prepared_repository_snapshot:${failureCode}`);
|
|
36
|
+
this.name = 'PreparedRepositorySnapshotError';
|
|
37
|
+
this.failureCode = failureCode;
|
|
38
|
+
this.site = site;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface PreparedCallSite {
|
|
43
|
+
sourceFile: string;
|
|
44
|
+
sourceLine: number;
|
|
45
|
+
callSiteStartOffset?: number;
|
|
46
|
+
callSiteEndOffset?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function preparedCallSnapshotError(
|
|
50
|
+
failureCode: PreparedSnapshotFailureCode,
|
|
51
|
+
factKind: 'outbound_call' | 'symbol_call',
|
|
52
|
+
call: PreparedCallSite,
|
|
53
|
+
): PreparedRepositorySnapshotError {
|
|
54
|
+
return new PreparedRepositorySnapshotError(failureCode, {
|
|
55
|
+
factKind,
|
|
56
|
+
sourceFile: call.sourceFile,
|
|
57
|
+
sourceLine: call.sourceLine,
|
|
58
|
+
callSiteStartOffset: call.callSiteStartOffset,
|
|
59
|
+
callSiteEndOffset: call.callSiteEndOffset,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isPreparedRepositorySnapshotError(
|
|
64
|
+
error: unknown,
|
|
65
|
+
): error is PreparedRepositorySnapshotError {
|
|
66
|
+
return error instanceof PreparedRepositorySnapshotError;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function recordPreparedSnapshotFailure(
|
|
70
|
+
db: Db,
|
|
71
|
+
repoId: number,
|
|
72
|
+
error: PreparedRepositorySnapshotError,
|
|
73
|
+
): void {
|
|
74
|
+
db.prepare(`UPDATE repositories SET index_status='failed',
|
|
75
|
+
error_count=1 WHERE id=?`).run(repoId);
|
|
76
|
+
db.prepare(`DELETE FROM diagnostics WHERE repo_id=? AND (
|
|
77
|
+
code IN ('index_failed_snapshot_preserved','source_read_failed')
|
|
78
|
+
OR code GLOB 'invalid_prepared_repository_snapshot:*'
|
|
79
|
+
)`).run(repoId);
|
|
80
|
+
db.prepare(`INSERT INTO diagnostics(
|
|
81
|
+
repo_id,severity,code,message,source_file,source_line
|
|
82
|
+
) VALUES(?,?,?,?,?,?)`).run(
|
|
83
|
+
repoId,
|
|
84
|
+
'error',
|
|
85
|
+
error.message,
|
|
86
|
+
'Index publication failed before commit for this repository; previous facts and fingerprint were preserved. '
|
|
87
|
+
+ `factKind=${error.site.factKind}`,
|
|
88
|
+
error.site.sourceFile,
|
|
89
|
+
error.site.sourceLine,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
2
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function parsed(value: string): unknown {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(value) as unknown;
|
|
8
|
+
} catch {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function hasSingleHopHelperReturn(value: unknown): boolean {
|
|
14
|
+
const chain = typeof value === 'string' ? parsed(value) : value;
|
|
15
|
+
return Array.isArray(chain) && chain.some((step) =>
|
|
16
|
+
record(step) && step.bindingOrigin === 'single_hop_helper_return');
|
|
17
|
+
}
|
package/src/db/migrations.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Db } from './connection.js';
|
|
2
2
|
import { schemaIndexesSql, schemaTablesSql } from './schema.js';
|
|
3
|
-
export const CURRENT_SCHEMA_VERSION =
|
|
3
|
+
export const CURRENT_SCHEMA_VERSION = 13;
|
|
4
4
|
const columns: Record<string, Array<{ name: string; ddl: string }>> = {
|
|
5
5
|
handler_methods: [
|
|
6
6
|
{ name: 'decorator_resolution_json', ddl: "ALTER TABLE handler_methods ADD COLUMN decorator_resolution_json TEXT NOT NULL DEFAULT '{}'" },
|
|
@@ -8,6 +8,9 @@ const columns: Record<string, Array<{ name: string; ddl: string }>> = {
|
|
|
8
8
|
service_bindings: [
|
|
9
9
|
{ name: 'helper_chain_json', ddl: 'ALTER TABLE service_bindings ADD COLUMN helper_chain_json TEXT' },
|
|
10
10
|
{ name: 'alias_expr', ddl: 'ALTER TABLE service_bindings ADD COLUMN alias_expr TEXT' },
|
|
11
|
+
{ name: 'binding_site_start_offset', ddl: 'ALTER TABLE service_bindings ADD COLUMN binding_site_start_offset INTEGER' },
|
|
12
|
+
{ name: 'binding_site_end_offset', ddl: 'ALTER TABLE service_bindings ADD COLUMN binding_site_end_offset INTEGER' },
|
|
13
|
+
{ name: 'owner_resolution', ddl: "ALTER TABLE service_bindings ADD COLUMN owner_resolution TEXT NOT NULL DEFAULT 'legacy_unknown' CHECK(owner_resolution IN ('owned_exact','ownerless_file_scope','legacy_unknown'))" },
|
|
11
14
|
],
|
|
12
15
|
repositories: [
|
|
13
16
|
{ name: 'fingerprint', ddl: 'ALTER TABLE repositories ADD COLUMN fingerprint TEXT' },
|
|
@@ -16,6 +19,7 @@ const columns: Record<string, Array<{ name: string; ddl: string }>> = {
|
|
|
16
19
|
{ name: 'graph_stale_reason', ddl: 'ALTER TABLE repositories ADD COLUMN graph_stale_reason TEXT' },
|
|
17
20
|
{ name: 'graph_stale_at', ddl: 'ALTER TABLE repositories ADD COLUMN graph_stale_at TEXT' },
|
|
18
21
|
{ name: 'fact_analyzer_version', ddl: "ALTER TABLE repositories ADD COLUMN fact_analyzer_version TEXT DEFAULT 'legacy'" },
|
|
22
|
+
{ name: 'package_public_surface_json', ddl: 'ALTER TABLE repositories ADD COLUMN package_public_surface_json TEXT' },
|
|
19
23
|
],
|
|
20
24
|
graph_edges: [
|
|
21
25
|
{ name: 'status', ddl: "ALTER TABLE graph_edges ADD COLUMN status TEXT NOT NULL DEFAULT 'unresolved'" },
|
|
@@ -81,7 +85,8 @@ function addMissingColumns(db: Db): void {
|
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
|
-
function normalizeLegacyStatus(db: Db): void {
|
|
88
|
+
function normalizeLegacyStatus(db: Db, priorVersion: number): void {
|
|
89
|
+
if (priorVersion >= 12) return;
|
|
85
90
|
db.prepare("UPDATE graph_edges SET status=CASE WHEN edge_type='REMOTE_CALL_RESOLVES_TO_OPERATION' THEN 'resolved' WHEN edge_type IN ('HANDLER_RUNS_DB_QUERY','HANDLER_CALLS_EXTERNAL_HTTP','HANDLER_EMITS_EVENT','EVENT_CONSUMED_BY_HANDLER') THEN 'terminal' WHEN edge_type='DYNAMIC_EDGE_CANDIDATE' THEN 'dynamic' WHEN status='ambiguous' THEN 'ambiguous' ELSE status END").run();
|
|
86
91
|
db.prepare("UPDATE repositories SET graph_stale_reason='schema_migration_requires_relink', graph_stale_at=COALESCE(graph_stale_at, datetime('now')) WHERE EXISTS (SELECT 1 FROM graph_edges WHERE graph_edges.workspace_id=repositories.workspace_id) AND graph_generation=0").run();
|
|
87
92
|
}
|
|
@@ -92,6 +97,13 @@ function markCallSiteMigrationStale(db: Db, priorVersion: number): void {
|
|
|
92
97
|
graph_stale_at=COALESCE(graph_stale_at,datetime('now'))
|
|
93
98
|
WHERE index_status='indexed' OR last_indexed_at IS NOT NULL`).run();
|
|
94
99
|
}
|
|
100
|
+
function markFactProvenanceMigrationStale(db: Db, priorVersion: number): void {
|
|
101
|
+
if (priorVersion >= 13) return;
|
|
102
|
+
db.prepare(`UPDATE repositories
|
|
103
|
+
SET graph_stale_reason='schema_v13_fact_provenance_requires_reindex',
|
|
104
|
+
graph_stale_at=COALESCE(graph_stale_at,datetime('now'))
|
|
105
|
+
WHERE index_status='indexed' OR last_indexed_at IS NOT NULL`).run();
|
|
106
|
+
}
|
|
95
107
|
export function migrate(db: Db): void {
|
|
96
108
|
db.transaction(() => {
|
|
97
109
|
const version = userVersion(db);
|
|
@@ -99,8 +111,9 @@ export function migrate(db: Db): void {
|
|
|
99
111
|
db.exec(schemaTablesSql);
|
|
100
112
|
addMissingColumns(db);
|
|
101
113
|
db.exec(schemaIndexesSql);
|
|
102
|
-
normalizeLegacyStatus(db);
|
|
114
|
+
normalizeLegacyStatus(db, version);
|
|
103
115
|
markCallSiteMigrationStale(db, version);
|
|
116
|
+
markFactProvenanceMigrationStale(db, version);
|
|
104
117
|
const violations = db.pragma('foreign_key_check');
|
|
105
118
|
if (violations.length > 0) throw new Error('SQLite foreign_key_check failed during migration');
|
|
106
119
|
db.pragma(`user_version = ${CURRENT_SCHEMA_VERSION}`);
|
package/src/db/repositories.ts
CHANGED
|
@@ -8,6 +8,14 @@ import type {
|
|
|
8
8
|
ServiceBindingFact,
|
|
9
9
|
ExecutableSymbolFact,
|
|
10
10
|
} from '../types.js';
|
|
11
|
+
import {
|
|
12
|
+
selectCallOwner,
|
|
13
|
+
type OwnerCandidate,
|
|
14
|
+
} from '../parsers/004-fact-identity.js';
|
|
15
|
+
import {
|
|
16
|
+
PreparedRepositorySnapshotError,
|
|
17
|
+
type PreparedSnapshotFailureCode,
|
|
18
|
+
} from './013-index-publication-failure.js';
|
|
11
19
|
export interface RepoRow {
|
|
12
20
|
id: number;
|
|
13
21
|
name: string;
|
|
@@ -28,6 +36,24 @@ export interface WorkspaceRow {
|
|
|
28
36
|
root_path: string;
|
|
29
37
|
db_path: string;
|
|
30
38
|
}
|
|
39
|
+
function initialPackagePublicSurface(packageName?: string): string {
|
|
40
|
+
return JSON.stringify({
|
|
41
|
+
schema: 'service-flow/package-public-surface@1',
|
|
42
|
+
status: packageName ? 'incomplete' : 'not_applicable',
|
|
43
|
+
reason: packageName ? 'package_surface_pending_index' : null,
|
|
44
|
+
recordCap: 256,
|
|
45
|
+
total: 0,
|
|
46
|
+
shown: 0,
|
|
47
|
+
omitted: 0,
|
|
48
|
+
packageName: packageName ?? null,
|
|
49
|
+
exportsPresent: false,
|
|
50
|
+
exportsAuthoritative: false,
|
|
51
|
+
main: null,
|
|
52
|
+
module: null,
|
|
53
|
+
entries: [],
|
|
54
|
+
scopes: [],
|
|
55
|
+
});
|
|
56
|
+
}
|
|
31
57
|
export function upsertWorkspace(
|
|
32
58
|
db: Db,
|
|
33
59
|
rootPath: string,
|
|
@@ -64,7 +90,15 @@ export function upsertRepository(
|
|
|
64
90
|
},
|
|
65
91
|
): number {
|
|
66
92
|
db.prepare(
|
|
67
|
-
`INSERT INTO repositories(workspace_id,name,absolute_path,relative_path,
|
|
93
|
+
`INSERT INTO repositories(workspace_id,name,absolute_path,relative_path,
|
|
94
|
+
package_name,package_version,dependencies_json,
|
|
95
|
+
package_public_surface_json,kind,is_git_repo)
|
|
96
|
+
VALUES(?,?,?,?,?,?,?,?,?,?)
|
|
97
|
+
ON CONFLICT(workspace_id,absolute_path) DO UPDATE SET
|
|
98
|
+
name=excluded.name,relative_path=excluded.relative_path,
|
|
99
|
+
package_name=excluded.package_name,
|
|
100
|
+
package_version=excluded.package_version,
|
|
101
|
+
dependencies_json=excluded.dependencies_json,kind=excluded.kind`,
|
|
68
102
|
).run(
|
|
69
103
|
workspaceId,
|
|
70
104
|
r.name,
|
|
@@ -73,6 +107,7 @@ export function upsertRepository(
|
|
|
73
107
|
r.packageName,
|
|
74
108
|
r.packageVersion,
|
|
75
109
|
JSON.stringify(r.dependencies ?? {}),
|
|
110
|
+
initialPackagePublicSurface(r.packageName),
|
|
76
111
|
r.kind ?? 'unknown',
|
|
77
112
|
r.isGitRepo ? 1 : 0,
|
|
78
113
|
);
|
|
@@ -355,16 +390,14 @@ export function insertBindings(
|
|
|
355
390
|
repoId: number,
|
|
356
391
|
rows: ServiceBindingFact[],
|
|
357
392
|
): void {
|
|
393
|
+
assertUniquePreparedBindingSites(rows);
|
|
358
394
|
const stmt = db.prepare(
|
|
359
|
-
'INSERT INTO service_bindings(repo_id,symbol_id,variable_name,alias,alias_expr,destination_expr,service_path_expr,is_dynamic,placeholders_json,source_file,source_line,helper_chain_json) VALUES(
|
|
395
|
+
'INSERT INTO service_bindings(repo_id,symbol_id,variable_name,alias,alias_expr,destination_expr,service_path_expr,is_dynamic,placeholders_json,source_file,source_line,binding_site_start_offset,binding_site_end_offset,owner_resolution,helper_chain_json) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
|
|
360
396
|
);
|
|
361
397
|
for (const r of rows)
|
|
362
398
|
stmt.run(
|
|
363
399
|
repoId,
|
|
364
|
-
repoId,
|
|
365
|
-
r.sourceFile,
|
|
366
|
-
r.sourceLine,
|
|
367
|
-
r.sourceLine,
|
|
400
|
+
bindingOwnerId(db, repoId, r),
|
|
368
401
|
r.variableName,
|
|
369
402
|
r.alias,
|
|
370
403
|
r.aliasExpr,
|
|
@@ -374,9 +407,100 @@ export function insertBindings(
|
|
|
374
407
|
JSON.stringify(r.placeholders),
|
|
375
408
|
r.sourceFile,
|
|
376
409
|
r.sourceLine,
|
|
410
|
+
r.bindingSiteStartOffset,
|
|
411
|
+
r.bindingSiteEndOffset,
|
|
412
|
+
r.ownerResolution,
|
|
377
413
|
r.helperChain ? JSON.stringify(r.helperChain) : null,
|
|
378
414
|
);
|
|
379
415
|
}
|
|
416
|
+
|
|
417
|
+
interface PersistedOwnerCandidate extends OwnerCandidate {
|
|
418
|
+
id: number;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function bindingSnapshotError(
|
|
422
|
+
failureCode: PreparedSnapshotFailureCode,
|
|
423
|
+
fact: ServiceBindingFact,
|
|
424
|
+
): PreparedRepositorySnapshotError {
|
|
425
|
+
return new PreparedRepositorySnapshotError(failureCode, {
|
|
426
|
+
factKind: 'service_binding',
|
|
427
|
+
sourceFile: fact.sourceFile,
|
|
428
|
+
sourceLine: fact.sourceLine,
|
|
429
|
+
callSiteStartOffset: fact.bindingSiteStartOffset,
|
|
430
|
+
callSiteEndOffset: fact.bindingSiteEndOffset,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function persistedOwnerCandidates(
|
|
435
|
+
db: Db,
|
|
436
|
+
repoId: number,
|
|
437
|
+
fact: ServiceBindingFact,
|
|
438
|
+
): PersistedOwnerCandidate[] {
|
|
439
|
+
const rows = db.prepare(`SELECT id,kind,qualified_name qualifiedName,
|
|
440
|
+
start_offset startOffset,end_offset endOffset FROM symbols
|
|
441
|
+
WHERE repo_id=? AND source_file=? AND start_offset<=? AND end_offset>=?`)
|
|
442
|
+
.all(
|
|
443
|
+
repoId, fact.sourceFile, fact.bindingSiteStartOffset,
|
|
444
|
+
fact.bindingSiteEndOffset,
|
|
445
|
+
);
|
|
446
|
+
return rows.flatMap((row) =>
|
|
447
|
+
typeof row.id === 'number' && typeof row.kind === 'string'
|
|
448
|
+
&& typeof row.qualifiedName === 'string'
|
|
449
|
+
&& typeof row.startOffset === 'number'
|
|
450
|
+
&& typeof row.endOffset === 'number'
|
|
451
|
+
? [{
|
|
452
|
+
id: row.id,
|
|
453
|
+
kind: row.kind,
|
|
454
|
+
qualifiedName: row.qualifiedName,
|
|
455
|
+
startOffset: row.startOffset,
|
|
456
|
+
endOffset: row.endOffset,
|
|
457
|
+
}]
|
|
458
|
+
: []);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function bindingOwnerId(
|
|
462
|
+
db: Db,
|
|
463
|
+
repoId: number,
|
|
464
|
+
fact: ServiceBindingFact,
|
|
465
|
+
): number | null {
|
|
466
|
+
const candidates = persistedOwnerCandidates(db, repoId, fact);
|
|
467
|
+
const selected = selectCallOwner(
|
|
468
|
+
candidates,
|
|
469
|
+
fact.bindingSiteStartOffset ?? -1,
|
|
470
|
+
fact.bindingSiteEndOffset ?? -1,
|
|
471
|
+
);
|
|
472
|
+
if (fact.ownerResolution === 'ownerless_file_scope') {
|
|
473
|
+
if (selected.status !== 'none')
|
|
474
|
+
throw bindingSnapshotError('binding_owner_mismatch', fact);
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
if (fact.ownerResolution !== 'owned_exact'
|
|
478
|
+
|| selected.status !== 'resolved'
|
|
479
|
+
|| selected.owner?.qualifiedName !== fact.sourceSymbolQualifiedName)
|
|
480
|
+
throw bindingSnapshotError('binding_owner_mismatch', fact);
|
|
481
|
+
const owner = selected.owner;
|
|
482
|
+
if (!owner)
|
|
483
|
+
throw bindingSnapshotError('binding_owner_mismatch', fact);
|
|
484
|
+
return owner.id;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function assertUniquePreparedBindingSites(
|
|
488
|
+
rows: readonly ServiceBindingFact[],
|
|
489
|
+
): void {
|
|
490
|
+
const seen = new Set<string>();
|
|
491
|
+
for (const row of rows) {
|
|
492
|
+
if (row.bindingSiteStartOffset === undefined
|
|
493
|
+
|| row.bindingSiteEndOffset === undefined)
|
|
494
|
+
throw bindingSnapshotError('binding_site_missing', row);
|
|
495
|
+
const key = [
|
|
496
|
+
row.sourceFile, row.variableName,
|
|
497
|
+
row.bindingSiteStartOffset, row.bindingSiteEndOffset,
|
|
498
|
+
].join('\u0000');
|
|
499
|
+
if (seen.has(key))
|
|
500
|
+
throw bindingSnapshotError('duplicate_service_binding_site', row);
|
|
501
|
+
seen.add(key);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
380
504
|
export function insertExecutableSymbols(db: Db, repoId: number, rows: ExecutableSymbolFact[]): void {
|
|
381
505
|
const stmt = db.prepare('INSERT INTO symbols(repo_id,file_id,kind,name,qualified_name,exported,start_line,end_line,start_offset,end_offset,source_file,exported_name,evidence_json) VALUES(?,(SELECT id FROM files WHERE repo_id=? AND relative_path=?),?,?,?,?,?,?,?,?,?,?,?)');
|
|
382
506
|
for (const r of rows) stmt.run(repoId, repoId, r.sourceFile, r.kind, r.localName, r.qualifiedName, r.exported ? 1 : 0, r.startLine, r.endLine, r.startOffset, r.endOffset, r.sourceFile, r.exportedName, r.importExportEvidence ? JSON.stringify(r.importExportEvidence) : null);
|
package/src/db/schema.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const schemaTablesSql = `
|
|
2
2
|
CREATE TABLE IF NOT EXISTS workspaces (id INTEGER PRIMARY KEY, root_path TEXT UNIQUE NOT NULL, db_path TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
|
|
3
|
-
CREATE TABLE IF NOT EXISTS repositories (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, name TEXT NOT NULL, absolute_path TEXT NOT NULL, relative_path TEXT NOT NULL, package_name TEXT, package_version TEXT, dependencies_json TEXT DEFAULT '{}', kind TEXT NOT NULL, is_git_repo INTEGER NOT NULL, last_indexed_at TEXT, index_status TEXT DEFAULT 'pending', error_count INTEGER DEFAULT 0, fingerprint TEXT, fact_generation INTEGER NOT NULL DEFAULT 0, graph_generation INTEGER NOT NULL DEFAULT 0, graph_stale_reason TEXT, graph_stale_at TEXT, fact_analyzer_version TEXT, UNIQUE(workspace_id, absolute_path), FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
3
|
+
CREATE TABLE IF NOT EXISTS repositories (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, name TEXT NOT NULL, absolute_path TEXT NOT NULL, relative_path TEXT NOT NULL, package_name TEXT, package_version TEXT, dependencies_json TEXT DEFAULT '{}', package_public_surface_json TEXT, kind TEXT NOT NULL, is_git_repo INTEGER NOT NULL, last_indexed_at TEXT, index_status TEXT DEFAULT 'pending', error_count INTEGER DEFAULT 0, fingerprint TEXT, fact_generation INTEGER NOT NULL DEFAULT 0, graph_generation INTEGER NOT NULL DEFAULT 0, graph_stale_reason TEXT, graph_stale_at TEXT, fact_analyzer_version TEXT, UNIQUE(workspace_id, absolute_path), FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
4
4
|
CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, relative_path TEXT NOT NULL, extension TEXT NOT NULL, sha256 TEXT NOT NULL, size_bytes INTEGER NOT NULL, last_indexed_at TEXT NOT NULL, UNIQUE(repo_id, relative_path), FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE);
|
|
5
5
|
CREATE TABLE IF NOT EXISTS cds_requires (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, alias TEXT NOT NULL, kind TEXT, model TEXT, destination TEXT, service_path TEXT, request_timeout INTEGER, raw_json TEXT NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE);
|
|
6
6
|
CREATE TABLE IF NOT EXISTS cds_services (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, namespace TEXT, service_name TEXT NOT NULL, qualified_name TEXT NOT NULL, service_path TEXT NOT NULL, is_extend INTEGER NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, extension_local_ref TEXT, extension_imported_symbol TEXT, extension_local_alias TEXT, extension_module_specifier TEXT, extension_import_kind TEXT, extension_base_service_id INTEGER, extension_base_status TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(extension_base_service_id) REFERENCES cds_services(id) ON DELETE SET NULL);
|
|
@@ -9,7 +9,7 @@ CREATE TABLE IF NOT EXISTS symbols (id INTEGER PRIMARY KEY, repo_id INTEGER NOT
|
|
|
9
9
|
CREATE TABLE IF NOT EXISTS handler_classes (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, class_name TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
10
10
|
CREATE TABLE IF NOT EXISTS handler_methods (id INTEGER PRIMARY KEY, handler_class_id INTEGER NOT NULL, method_name TEXT NOT NULL, decorator_kind TEXT NOT NULL, decorator_value TEXT, decorator_raw_expression TEXT NOT NULL, decorator_resolution_json TEXT NOT NULL DEFAULT '{}', source_file TEXT NOT NULL, source_line INTEGER NOT NULL, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE CASCADE);
|
|
11
11
|
CREATE TABLE IF NOT EXISTS handler_registrations (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, handler_class_id INTEGER, class_name TEXT, import_source TEXT, registration_file TEXT NOT NULL, registration_line INTEGER NOT NULL, registration_kind TEXT NOT NULL, confidence REAL NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(handler_class_id) REFERENCES handler_classes(id) ON DELETE SET NULL);
|
|
12
|
-
CREATE TABLE IF NOT EXISTS service_bindings (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, variable_name TEXT NOT NULL, alias TEXT, alias_expr TEXT, destination_expr TEXT, service_path_expr TEXT, is_dynamic INTEGER NOT NULL, placeholders_json TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, helper_chain_json TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
12
|
+
CREATE TABLE IF NOT EXISTS service_bindings (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, symbol_id INTEGER, variable_name TEXT NOT NULL, alias TEXT, alias_expr TEXT, destination_expr TEXT, service_path_expr TEXT, is_dynamic INTEGER NOT NULL, placeholders_json TEXT NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, binding_site_start_offset INTEGER, binding_site_end_offset INTEGER, owner_resolution TEXT NOT NULL DEFAULT 'legacy_unknown' CHECK(owner_resolution IN ('owned_exact','ownerless_file_scope','legacy_unknown')), helper_chain_json TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
13
13
|
CREATE TABLE IF NOT EXISTS outbound_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, source_symbol_id INTEGER, call_type TEXT NOT NULL, service_binding_id INTEGER, method TEXT, operation_path_expr TEXT, query_entity TEXT, event_name_expr TEXT, payload_summary TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, call_site_start_offset INTEGER, call_site_end_offset INTEGER, confidence REAL NOT NULL, unresolved_reason TEXT, local_service_name TEXT, local_service_lookup TEXT, alias_chain_json TEXT, evidence_json TEXT, external_target_kind TEXT, external_target_id TEXT, external_target_label TEXT, external_target_dynamic INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(source_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL, FOREIGN KEY(service_binding_id) REFERENCES service_bindings(id) ON DELETE SET NULL);
|
|
14
14
|
CREATE TABLE IF NOT EXISTS symbol_calls (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, caller_symbol_id INTEGER NOT NULL, callee_symbol_id INTEGER, callee_expression TEXT NOT NULL, import_source TEXT, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, call_site_start_offset INTEGER, call_site_end_offset INTEGER, call_role TEXT NOT NULL DEFAULT 'legacy_unknown', status TEXT NOT NULL, confidence REAL NOT NULL, evidence_json TEXT NOT NULL, unresolved_reason TEXT, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE, FOREIGN KEY(caller_symbol_id) REFERENCES symbols(id) ON DELETE CASCADE, FOREIGN KEY(callee_symbol_id) REFERENCES symbols(id) ON DELETE SET NULL);
|
|
15
15
|
CREATE TABLE IF NOT EXISTS graph_edges (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, edge_type TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'unresolved', from_kind TEXT NOT NULL, from_id TEXT NOT NULL, to_kind TEXT NOT NULL, to_id TEXT NOT NULL, confidence REAL NOT NULL, evidence_json TEXT NOT NULL, is_dynamic INTEGER NOT NULL, unresolved_reason TEXT, generation INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
@@ -27,6 +27,8 @@ CREATE INDEX IF NOT EXISTS idx_calls_repo ON outbound_calls(repo_id, call_type);
|
|
|
27
27
|
CREATE INDEX IF NOT EXISTS idx_symbol_calls_caller ON symbol_calls(repo_id, caller_symbol_id);
|
|
28
28
|
CREATE INDEX IF NOT EXISTS idx_outbound_call_site ON outbound_calls(repo_id, source_file, call_site_start_offset, call_site_end_offset, call_type);
|
|
29
29
|
CREATE INDEX IF NOT EXISTS idx_symbol_call_site_role ON symbol_calls(repo_id, source_file, call_site_start_offset, call_site_end_offset, call_role);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_service_binding_site ON service_bindings(repo_id,source_file,variable_name,binding_site_start_offset,binding_site_end_offset);
|
|
31
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_service_binding_exact_site ON service_bindings(repo_id,source_file,variable_name,binding_site_start_offset,binding_site_end_offset) WHERE binding_site_start_offset IS NOT NULL AND binding_site_end_offset IS NOT NULL;
|
|
30
32
|
CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(kind, name, path, repo);
|
|
31
33
|
`;
|
|
32
34
|
|
package/src/index.ts
CHANGED
|
@@ -35,3 +35,15 @@ export type {
|
|
|
35
35
|
export { parseImplementationHint } from './trace/implementation-hints.js';
|
|
36
36
|
export type { DynamicMode, ImplementationHint, TraceOptions } from './types.js';
|
|
37
37
|
export { redactValue, redactText } from './utils/redaction.js';
|
|
38
|
+
export type { Db } from './db/connection.js';
|
|
39
|
+
export type {
|
|
40
|
+
CallType,
|
|
41
|
+
EdgeType,
|
|
42
|
+
ExecutableSymbolFact,
|
|
43
|
+
OutboundCallFact,
|
|
44
|
+
SymbolCallFact,
|
|
45
|
+
SymbolCallRole,
|
|
46
|
+
TraceEdge,
|
|
47
|
+
TraceResult,
|
|
48
|
+
TraceStart,
|
|
49
|
+
} from './types.js';
|