@saptools/service-flow 0.1.70 → 0.1.73
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 +21 -0
- package/README.md +23 -6
- package/TECHNICAL-NOTE.md +24 -2
- package/dist/{chunk-GSLFY6J2.js → chunk-32WOTGTS.js} +12061 -9288
- package/dist/chunk-32WOTGTS.js.map +1 -0
- package/dist/cli.js +854 -161
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +54 -14
- package/dist/index.js +2 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/doctor-event-quality.ts +446 -0
- package/src/cli/doctor.ts +4 -6
- package/src/cli.ts +35 -13
- package/src/config/workspace-config.ts +15 -0
- package/src/db/call-fact-repository.ts +6 -3
- package/src/db/current-fact-semantics.ts +5 -29
- package/src/db/event-fact-semantics.ts +546 -0
- package/src/db/event-site-semantics.ts +62 -0
- package/src/db/event-surface-invalidation.ts +38 -0
- package/src/db/fact-json-inventory.ts +16 -0
- package/src/db/fact-lifecycle.ts +70 -12
- package/src/db/migrations.ts +15 -1
- package/src/db/package-target-invalidation.ts +80 -0
- package/src/db/repositories.ts +28 -2
- package/src/db/schema-structure.ts +41 -1
- package/src/db/schema.ts +6 -2
- package/src/indexer/repository-indexer.ts +93 -10
- package/src/indexer/workspace-indexer.ts +13 -3
- package/src/linker/cross-repo-linker.ts +27 -3
- package/src/linker/event-environment-link.ts +211 -0
- package/src/linker/event-shape-candidate-linker.ts +204 -0
- package/src/linker/event-subscription-handler-linker.ts +220 -25
- package/src/linker/event-template-link.ts +40 -6
- package/src/linker/package-event-constant-resolver.ts +302 -0
- package/src/output/repository-inspection.ts +11 -0
- package/src/output/table-output.ts +13 -1
- package/src/parsers/decorator-parser.ts +9 -53
- package/src/parsers/environment-declarations.ts +404 -0
- package/src/parsers/event-call-analysis.ts +370 -0
- package/src/parsers/event-environment-reference.ts +242 -0
- package/src/parsers/event-loop-registration.ts +132 -0
- package/src/parsers/event-name-import-resolution.ts +243 -0
- package/src/parsers/event-receiver-analysis.ts +394 -0
- package/src/parsers/event-subscription-facts.ts +4 -0
- package/src/parsers/generated-constants-parser.ts +80 -14
- package/src/parsers/outbound-call-classifier.ts +27 -124
- package/src/parsers/outbound-call-parser.ts +13 -1
- package/src/parsers/outbound-expression-analysis.ts +2 -2
- package/src/parsers/stable-local-value.ts +42 -10
- package/src/parsers/string-constant-lookups.ts +408 -0
- package/src/trace/edge-target.ts +65 -0
- package/src/trace/event-runtime-resolution.ts +24 -3
- package/src/trace/event-shape-candidate-trace.ts +172 -0
- package/src/trace/event-subscriber-traversal.ts +90 -8
- package/src/trace/evidence.ts +7 -28
- package/src/trace/trace-scope-execution.ts +22 -30
- package/src/types.ts +19 -1
- package/src/utils/event-skeleton.ts +207 -0
- package/src/version.ts +1 -1
- package/dist/chunk-GSLFY6J2.js.map +0 -1
package/src/db/fact-lifecycle.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type SchemaStructureCategoryCount,
|
|
15
15
|
} from './schema-structure.js';
|
|
16
16
|
import { ANALYZER_VERSION } from '../version.js';
|
|
17
|
+
import { invalidEventFactExamples } from './event-fact-semantics.js';
|
|
17
18
|
|
|
18
19
|
export type FactLifecycleCode =
|
|
19
20
|
| 'schema_upgrade_required'
|
|
@@ -27,11 +28,8 @@ export interface FactLifecycleDiagnostic extends Record<string, unknown> {
|
|
|
27
28
|
remediation: string;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
const remediation = [
|
|
31
|
-
'service-flow index --workspace /workspace --force',
|
|
32
|
-
'service-flow link --workspace /workspace --force',
|
|
33
|
-
].join('\n');
|
|
34
31
|
const CATEGORY_LIMIT = 24;
|
|
32
|
+
const EXAMPLE_LIMIT = 5;
|
|
35
33
|
|
|
36
34
|
function count(db: Db, sql: string, ...params: unknown[]): number {
|
|
37
35
|
const row = db.prepare(sql).get(...params);
|
|
@@ -51,12 +49,13 @@ export function factLifecycleDiagnostic(
|
|
|
51
49
|
workspaceId?: number,
|
|
52
50
|
phase: PackageFactPhase = 'pre_package',
|
|
53
51
|
): FactLifecycleDiagnostic | undefined {
|
|
54
|
-
return schemaLifecycleDiagnostic(db)
|
|
52
|
+
return schemaLifecycleDiagnostic(db, workspaceId)
|
|
55
53
|
?? currentFactLifecycleDiagnostic(db, workspaceId, phase);
|
|
56
54
|
}
|
|
57
55
|
|
|
58
56
|
export function schemaLifecycleDiagnostic(
|
|
59
57
|
db: Db,
|
|
58
|
+
workspaceId?: number,
|
|
60
59
|
): FactLifecycleDiagnostic | undefined {
|
|
61
60
|
const currentSchema = schemaVersion(db);
|
|
62
61
|
if (currentSchema > CURRENT_SCHEMA_VERSION) return {
|
|
@@ -71,13 +70,15 @@ export function schemaLifecycleDiagnostic(
|
|
|
71
70
|
severity: 'error',
|
|
72
71
|
code: 'schema_upgrade_required',
|
|
73
72
|
message: `Database schema ${currentSchema} must be upgraded to ${CURRENT_SCHEMA_VERSION} before this command can read current call-site facts.`,
|
|
74
|
-
remediation,
|
|
73
|
+
remediation: staleRemediation(db, workspaceId),
|
|
75
74
|
currentSchemaVersion: currentSchema,
|
|
76
75
|
requiredSchemaVersion: CURRENT_SCHEMA_VERSION,
|
|
77
76
|
};
|
|
78
77
|
const structureCategories = invalidSchemaStructureCategories(db);
|
|
79
78
|
if (structureCategories.length > 0)
|
|
80
|
-
return reindexDiagnostic(
|
|
79
|
+
return reindexDiagnostic(
|
|
80
|
+
db, workspaceId, 0, structureCategories, 'pre_package', false,
|
|
81
|
+
);
|
|
81
82
|
return undefined;
|
|
82
83
|
}
|
|
83
84
|
|
|
@@ -88,15 +89,17 @@ export function currentFactLifecycleDiagnostic(
|
|
|
88
89
|
): FactLifecycleDiagnostic | undefined {
|
|
89
90
|
const staleRepositories = oldAnalyzerCount(db, workspaceId);
|
|
90
91
|
if (staleRepositories > 0)
|
|
91
|
-
return reindexDiagnostic(
|
|
92
|
+
return reindexDiagnostic(
|
|
93
|
+
db, workspaceId, staleRepositories, [], phase,
|
|
94
|
+
);
|
|
92
95
|
const jsonCategories = invalidFactJsonCategories(db, workspaceId);
|
|
93
96
|
if (jsonCategories.length > 0)
|
|
94
|
-
return reindexDiagnostic(0, jsonCategories);
|
|
97
|
+
return reindexDiagnostic(db, workspaceId, 0, jsonCategories, phase);
|
|
95
98
|
const semanticCategories = invalidFactSemanticCategories(
|
|
96
99
|
db, workspaceId, phase,
|
|
97
100
|
);
|
|
98
101
|
if (semanticCategories.length === 0) return undefined;
|
|
99
|
-
return reindexDiagnostic(0, semanticCategories);
|
|
102
|
+
return reindexDiagnostic(db, workspaceId, 0, semanticCategories, phase);
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
type InvalidFactCategory =
|
|
@@ -105,26 +108,81 @@ type InvalidFactCategory =
|
|
|
105
108
|
| SchemaStructureCategoryCount;
|
|
106
109
|
|
|
107
110
|
function reindexDiagnostic(
|
|
111
|
+
db: Db,
|
|
112
|
+
workspaceId: number | undefined,
|
|
108
113
|
staleRepositories: number,
|
|
109
114
|
categories: InvalidFactCategory[],
|
|
115
|
+
phase: PackageFactPhase,
|
|
116
|
+
examplesAllowed = true,
|
|
110
117
|
): FactLifecycleDiagnostic {
|
|
111
118
|
const invalidFacts = categories.reduce((sum, item) => sum + item.count, 0);
|
|
112
119
|
const shown = categories.slice(0, CATEGORY_LIMIT);
|
|
120
|
+
const eventExamples = examplesAllowed
|
|
121
|
+
? invalidEventFactExamples(db, workspaceId, phase, EXAMPLE_LIMIT)
|
|
122
|
+
: { total: 0, affectedRepositoryCount: 0, examples: [] };
|
|
123
|
+
const examples = invalidFacts > 0 ? eventExamples.examples : [];
|
|
113
124
|
return {
|
|
114
125
|
severity: 'error',
|
|
115
126
|
code: 'reindex_required',
|
|
116
|
-
message:
|
|
117
|
-
|
|
127
|
+
message: invalidFacts > 0
|
|
128
|
+
? 'Current facts fail bounded semantic integrity checks; inspect the offending rows before rebuilding graph edges.'
|
|
129
|
+
: 'Current facts are stale; force index and link before tracing or rebuilding graph edges.',
|
|
130
|
+
remediation: invalidFacts > 0
|
|
131
|
+
? invalidFactRemediation(db, workspaceId)
|
|
132
|
+
: staleRemediation(db, workspaceId),
|
|
118
133
|
staleRepositoryCount: staleRepositories,
|
|
119
134
|
invalidCallFactCount: invalidFacts,
|
|
120
135
|
invalidFactCategories: shown,
|
|
121
136
|
invalidFactCategoryCount: categories.length,
|
|
122
137
|
shownInvalidFactCategoryCount: shown.length,
|
|
123
138
|
omittedInvalidFactCategoryCount: categories.length - shown.length,
|
|
139
|
+
affectedRepositoryCount: eventExamples.affectedRepositoryCount,
|
|
140
|
+
workspaceRepositoryCount: repositoryCount(db, workspaceId),
|
|
141
|
+
invalidFactExamples: examples,
|
|
142
|
+
invalidFactExampleCount: eventExamples.total,
|
|
143
|
+
shownInvalidFactExampleCount: examples.length,
|
|
144
|
+
omittedInvalidFactExampleCount:
|
|
145
|
+
Math.max(0, eventExamples.total - examples.length),
|
|
124
146
|
requiredAnalyzerVersion: ANALYZER_VERSION,
|
|
125
147
|
};
|
|
126
148
|
}
|
|
127
149
|
|
|
150
|
+
function workspacePath(db: Db, workspaceId?: number): string | undefined {
|
|
151
|
+
if (workspaceId === undefined) return undefined;
|
|
152
|
+
const row = db.prepare(
|
|
153
|
+
'SELECT root_path rootPath FROM workspaces WHERE id=?',
|
|
154
|
+
).get(workspaceId);
|
|
155
|
+
return typeof row?.rootPath === 'string' ? row.rootPath : undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function shellQuote(value: string): string {
|
|
159
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function workspaceArgument(db: Db, workspaceId?: number): string {
|
|
163
|
+
const root = workspacePath(db, workspaceId);
|
|
164
|
+
return root ? ` --workspace ${shellQuote(root)}` : '';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function staleRemediation(db: Db, workspaceId?: number): string {
|
|
168
|
+
const workspace = workspaceArgument(db, workspaceId);
|
|
169
|
+
return [
|
|
170
|
+
`service-flow index${workspace} --force`,
|
|
171
|
+
`service-flow link${workspace} --force`,
|
|
172
|
+
].join('\n');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function invalidFactRemediation(db: Db, workspaceId?: number): string {
|
|
176
|
+
return `service-flow doctor${workspaceArgument(
|
|
177
|
+
db, workspaceId,
|
|
178
|
+
)} --strict --detail`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function repositoryCount(db: Db, workspaceId?: number): number {
|
|
182
|
+
return count(db, `SELECT COUNT(*) count FROM repositories
|
|
183
|
+
WHERE (? IS NULL OR workspace_id=?)`, workspaceId, workspaceId);
|
|
184
|
+
}
|
|
185
|
+
|
|
128
186
|
export function assertWorkspaceLinkable(
|
|
129
187
|
db: Db,
|
|
130
188
|
workspaceId: number,
|
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 = 14;
|
|
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 '{}'" },
|
|
@@ -20,6 +20,7 @@ const columns: Record<string, Array<{ name: string; ddl: string }>> = {
|
|
|
20
20
|
{ name: 'graph_stale_at', ddl: 'ALTER TABLE repositories ADD COLUMN graph_stale_at TEXT' },
|
|
21
21
|
{ name: 'fact_analyzer_version', ddl: "ALTER TABLE repositories ADD COLUMN fact_analyzer_version TEXT DEFAULT 'legacy'" },
|
|
22
22
|
{ name: 'package_public_surface_json', ddl: 'ALTER TABLE repositories ADD COLUMN package_public_surface_json TEXT' },
|
|
23
|
+
{ name: 'environment_declarations_json', ddl: `ALTER TABLE repositories ADD COLUMN environment_declarations_json TEXT DEFAULT '{"schema":"service-flow/environment-declarations@1","allowedKeys":["SHARD_CODE"],"status":"not_applicable","reason":null,"recordCap":32,"total":0,"shown":0,"omitted":0,"declarations":[]}'` },
|
|
23
24
|
],
|
|
24
25
|
graph_edges: [
|
|
25
26
|
{ name: 'status', ddl: "ALTER TABLE graph_edges ADD COLUMN status TEXT NOT NULL DEFAULT 'unresolved'" },
|
|
@@ -60,6 +61,8 @@ const columns: Record<string, Array<{ name: string; ddl: string }>> = {
|
|
|
60
61
|
{ name: 'external_target_dynamic', ddl: 'ALTER TABLE outbound_calls ADD COLUMN external_target_dynamic INTEGER NOT NULL DEFAULT 0' },
|
|
61
62
|
{ name: 'call_site_start_offset', ddl: 'ALTER TABLE outbound_calls ADD COLUMN call_site_start_offset INTEGER' },
|
|
62
63
|
{ name: 'call_site_end_offset', ddl: 'ALTER TABLE outbound_calls ADD COLUMN call_site_end_offset INTEGER' },
|
|
64
|
+
{ name: 'event_skeleton_signature', ddl: 'ALTER TABLE outbound_calls ADD COLUMN event_skeleton_signature TEXT' },
|
|
65
|
+
{ name: 'event_skeleton_json', ddl: 'ALTER TABLE outbound_calls ADD COLUMN event_skeleton_json TEXT' },
|
|
63
66
|
],
|
|
64
67
|
symbol_calls: [
|
|
65
68
|
{ name: 'call_site_start_offset', ddl: 'ALTER TABLE symbol_calls ADD COLUMN call_site_start_offset INTEGER' },
|
|
@@ -104,6 +107,16 @@ function markFactProvenanceMigrationStale(db: Db, priorVersion: number): void {
|
|
|
104
107
|
graph_stale_at=COALESCE(graph_stale_at,datetime('now'))
|
|
105
108
|
WHERE index_status='indexed' OR last_indexed_at IS NOT NULL`).run();
|
|
106
109
|
}
|
|
110
|
+
function markEventSurfaceMigrationStale(db: Db, priorVersion: number): void {
|
|
111
|
+
if (priorVersion >= 14) return;
|
|
112
|
+
db.prepare(
|
|
113
|
+
'UPDATE repositories SET environment_declarations_json=NULL',
|
|
114
|
+
).run();
|
|
115
|
+
db.prepare(`UPDATE repositories
|
|
116
|
+
SET graph_stale_reason='schema_v14_event_surface_requires_reindex',
|
|
117
|
+
graph_stale_at=COALESCE(graph_stale_at,datetime('now'))
|
|
118
|
+
WHERE index_status='indexed' OR last_indexed_at IS NOT NULL`).run();
|
|
119
|
+
}
|
|
107
120
|
export function migrate(db: Db): void {
|
|
108
121
|
db.transaction(() => {
|
|
109
122
|
const version = userVersion(db);
|
|
@@ -114,6 +127,7 @@ export function migrate(db: Db): void {
|
|
|
114
127
|
normalizeLegacyStatus(db, version);
|
|
115
128
|
markCallSiteMigrationStale(db, version);
|
|
116
129
|
markFactProvenanceMigrationStale(db, version);
|
|
130
|
+
markEventSurfaceMigrationStale(db, version);
|
|
117
131
|
const violations = db.pragma('foreign_key_check');
|
|
118
132
|
if (violations.length > 0) throw new Error('SQLite foreign_key_check failed during migration');
|
|
119
133
|
db.pragma(`user_version = ${CURRENT_SCHEMA_VERSION}`);
|
|
@@ -10,6 +10,13 @@ interface PackageCallRow {
|
|
|
10
10
|
evidence: Record<string, unknown>;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
interface PackageEventCallRow {
|
|
14
|
+
id: number;
|
|
15
|
+
repoId: number;
|
|
16
|
+
unresolvedReason?: string | null;
|
|
17
|
+
evidence: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
export interface PackageInvalidationBatch {
|
|
14
21
|
publishingRepoIds: ReadonlySet<number>;
|
|
15
22
|
affectedCallerRepoIds: Set<number>;
|
|
@@ -44,6 +51,14 @@ function packageName(evidence: Record<string, unknown>): string | undefined {
|
|
|
44
51
|
?.requestedPackageName ?? undefined;
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
function eventPackageName(
|
|
55
|
+
evidence: Record<string, unknown>,
|
|
56
|
+
): string | undefined {
|
|
57
|
+
return parsePackageImportReference(
|
|
58
|
+
evidence.eventNameConstantImportBinding,
|
|
59
|
+
)?.requestedPackageName ?? undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
47
62
|
function packageCallEvidenceValid(
|
|
48
63
|
evidence: Record<string, unknown>,
|
|
49
64
|
): boolean {
|
|
@@ -94,6 +109,68 @@ function pendingEvidence(evidence: Record<string, unknown>): string {
|
|
|
94
109
|
});
|
|
95
110
|
}
|
|
96
111
|
|
|
112
|
+
function currentEventCalls(
|
|
113
|
+
db: Db,
|
|
114
|
+
workspaceId: number,
|
|
115
|
+
targetRepoId: number,
|
|
116
|
+
): PackageEventCallRow[] {
|
|
117
|
+
const rows = db.prepare(`SELECT c.id,c.repo_id repoId,
|
|
118
|
+
c.unresolved_reason unresolvedReason,c.evidence_json evidenceJson
|
|
119
|
+
FROM outbound_calls c JOIN repositories r ON r.id=c.repo_id
|
|
120
|
+
WHERE r.workspace_id=? AND r.id<>? AND r.fact_analyzer_version=?
|
|
121
|
+
AND json_extract(c.evidence_json,
|
|
122
|
+
'$.eventNameConstantImportBinding.moduleKind')='package'
|
|
123
|
+
ORDER BY c.id`).all(workspaceId, targetRepoId, ANALYZER_VERSION);
|
|
124
|
+
return rows.flatMap((row) => {
|
|
125
|
+
const evidence = parsedEvidence(row.evidenceJson);
|
|
126
|
+
return evidence && eventPackageName(evidence)
|
|
127
|
+
&& typeof row.id === 'number' && typeof row.repoId === 'number'
|
|
128
|
+
? [{
|
|
129
|
+
id: row.id, repoId: row.repoId, evidence,
|
|
130
|
+
unresolvedReason: typeof row.unresolvedReason === 'string'
|
|
131
|
+
? row.unresolvedReason : null,
|
|
132
|
+
}] : [];
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function pendingEventEvidence(
|
|
137
|
+
evidence: Record<string, unknown>,
|
|
138
|
+
): string {
|
|
139
|
+
const parser = { ...evidence };
|
|
140
|
+
delete parser.eventNameConstant;
|
|
141
|
+
delete parser.eventNamePackageConstantResolution;
|
|
142
|
+
parser.eventNameUnresolvedReason = 'event_name_constant_resolution_pending';
|
|
143
|
+
parser.eventNameStatus = 'dynamic';
|
|
144
|
+
parser.eventNameSourceKind = 'dynamic_expression';
|
|
145
|
+
parser.eventNamePlaceholderKeys = [];
|
|
146
|
+
return JSON.stringify(parser);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function resetPackageEventCalls(
|
|
150
|
+
db: Db,
|
|
151
|
+
workspaceId: number,
|
|
152
|
+
targetRepoId: number,
|
|
153
|
+
names: ReadonlySet<string>,
|
|
154
|
+
batch: PackageInvalidationBatch,
|
|
155
|
+
): boolean {
|
|
156
|
+
const update = db.prepare(`UPDATE outbound_calls SET event_name_expr=?,
|
|
157
|
+
unresolved_reason=?,evidence_json=? WHERE id=?`);
|
|
158
|
+
let matched = false;
|
|
159
|
+
for (const call of currentEventCalls(db, workspaceId, targetRepoId)) {
|
|
160
|
+
if (!names.has(eventPackageName(call.evidence) ?? '')) continue;
|
|
161
|
+
const source = call.evidence.eventNameConstantSourceExpression;
|
|
162
|
+
if (typeof source !== 'string' || source.length === 0)
|
|
163
|
+
throw new Error('invalid_current_package_event_constant_evidence');
|
|
164
|
+
update.run(
|
|
165
|
+
source, 'event_name_constant_resolution_pending',
|
|
166
|
+
pendingEventEvidence(call.evidence), call.id,
|
|
167
|
+
);
|
|
168
|
+
batch.affectedCallerRepoIds.add(call.repoId);
|
|
169
|
+
matched = true;
|
|
170
|
+
}
|
|
171
|
+
return matched;
|
|
172
|
+
}
|
|
173
|
+
|
|
97
174
|
function targetWorkspace(
|
|
98
175
|
db: Db,
|
|
99
176
|
repoId: number,
|
|
@@ -142,6 +219,9 @@ export function invalidatePackageTargetFacts(
|
|
|
142
219
|
batch.affectedCallerRepoIds.add(call.repoId);
|
|
143
220
|
matched = true;
|
|
144
221
|
}
|
|
222
|
+
matched = resetPackageEventCalls(
|
|
223
|
+
db, target.workspaceId, targetRepoId, names, batch,
|
|
224
|
+
) || matched;
|
|
145
225
|
if (matched || packageIdentityChanged(
|
|
146
226
|
target.packageName, newPackageName,
|
|
147
227
|
)) batch.affectedWorkspaceIds.add(target.workspaceId);
|
package/src/db/repositories.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
HandlerRegistrationFact,
|
|
8
8
|
ServiceBindingFact,
|
|
9
9
|
ExecutableSymbolFact,
|
|
10
|
+
GeneratedConstantFact,
|
|
10
11
|
} from '../types.js';
|
|
11
12
|
import {
|
|
12
13
|
selectCallOwner,
|
|
@@ -16,6 +17,9 @@ import {
|
|
|
16
17
|
PreparedRepositorySnapshotError,
|
|
17
18
|
type PreparedSnapshotFailureCode,
|
|
18
19
|
} from './index-publication-failure.js';
|
|
20
|
+
import {
|
|
21
|
+
emptyEnvironmentDeclarations,
|
|
22
|
+
} from '../parsers/environment-declarations.js';
|
|
19
23
|
export interface RepoRow {
|
|
20
24
|
id: number;
|
|
21
25
|
name: string;
|
|
@@ -92,8 +96,8 @@ export function upsertRepository(
|
|
|
92
96
|
db.prepare(
|
|
93
97
|
`INSERT INTO repositories(workspace_id,name,absolute_path,relative_path,
|
|
94
98
|
package_name,package_version,dependencies_json,
|
|
95
|
-
package_public_surface_json,kind,is_git_repo)
|
|
96
|
-
VALUES(
|
|
99
|
+
package_public_surface_json,environment_declarations_json,kind,is_git_repo)
|
|
100
|
+
VALUES(?,?,?,?,?,?,?,?,?,?,?)
|
|
97
101
|
ON CONFLICT(workspace_id,absolute_path) DO UPDATE SET
|
|
98
102
|
name=excluded.name,relative_path=excluded.relative_path,
|
|
99
103
|
package_name=excluded.package_name,
|
|
@@ -108,6 +112,7 @@ export function upsertRepository(
|
|
|
108
112
|
r.packageVersion,
|
|
109
113
|
JSON.stringify(r.dependencies ?? {}),
|
|
110
114
|
initialPackagePublicSurface(r.packageName),
|
|
115
|
+
JSON.stringify(emptyEnvironmentDeclarations()),
|
|
111
116
|
r.kind ?? 'unknown',
|
|
112
117
|
r.isGitRepo ? 1 : 0,
|
|
113
118
|
);
|
|
@@ -152,6 +157,7 @@ export function clearRepoFacts(db: Db, repoId: number): void {
|
|
|
152
157
|
'symbol_calls',
|
|
153
158
|
'handler_registrations',
|
|
154
159
|
'service_bindings',
|
|
160
|
+
'generated_constants',
|
|
155
161
|
'symbols',
|
|
156
162
|
'diagnostics',
|
|
157
163
|
'files',
|
|
@@ -159,6 +165,26 @@ export function clearRepoFacts(db: Db, repoId: number): void {
|
|
|
159
165
|
db.prepare(`DELETE FROM ${t} WHERE repo_id=?`).run(repoId);
|
|
160
166
|
db.prepare('DELETE FROM search_index WHERE repo=?').run(String(repoId));
|
|
161
167
|
}
|
|
168
|
+
export function insertGeneratedConstants(
|
|
169
|
+
db: Db,
|
|
170
|
+
repoId: number,
|
|
171
|
+
rows: GeneratedConstantFact[],
|
|
172
|
+
): void {
|
|
173
|
+
const stmt = db.prepare(`INSERT INTO generated_constants(
|
|
174
|
+
repo_id,source_file,source_line,name,container_name,member_name,value,
|
|
175
|
+
constant_kind,exported,stable,resolution_status,unresolved_reason,
|
|
176
|
+
declaration_start_offset,declaration_end_offset,
|
|
177
|
+
value_start_offset,value_end_offset
|
|
178
|
+
) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`);
|
|
179
|
+
for (const row of rows) stmt.run(
|
|
180
|
+
repoId, row.sourceFile, row.sourceLine, row.name,
|
|
181
|
+
row.containerName ?? null, row.memberName ?? null, row.value ?? null,
|
|
182
|
+
row.constantKind, row.exported ? 1 : 0, row.stable ? 1 : 0,
|
|
183
|
+
row.resolutionStatus, row.unresolvedReason ?? null,
|
|
184
|
+
row.declarationStartOffset, row.declarationEndOffset,
|
|
185
|
+
row.valueStartOffset, row.valueEndOffset,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
162
188
|
export function insertRequires(
|
|
163
189
|
db: Db,
|
|
164
190
|
repoId: number,
|
|
@@ -12,7 +12,8 @@ const requiredColumns = {
|
|
|
12
12
|
repositories: [
|
|
13
13
|
'id', 'workspace_id', 'name', 'absolute_path', 'relative_path',
|
|
14
14
|
'package_name', 'package_version', 'dependencies_json',
|
|
15
|
-
'package_public_surface_json', '
|
|
15
|
+
'package_public_surface_json', 'environment_declarations_json',
|
|
16
|
+
'kind', 'is_git_repo', 'last_indexed_at',
|
|
16
17
|
'index_status', 'error_count', 'fingerprint', 'fact_generation',
|
|
17
18
|
'graph_generation', 'graph_stale_reason', 'graph_stale_at',
|
|
18
19
|
'fact_analyzer_version',
|
|
@@ -66,6 +67,7 @@ const requiredColumns = {
|
|
|
66
67
|
outbound_calls: [
|
|
67
68
|
'id', 'repo_id', 'source_symbol_id', 'service_binding_id', 'call_type',
|
|
68
69
|
'method', 'operation_path_expr', 'query_entity', 'event_name_expr',
|
|
70
|
+
'event_skeleton_signature', 'event_skeleton_json',
|
|
69
71
|
'payload_summary', 'source_file', 'source_line',
|
|
70
72
|
'call_site_start_offset', 'call_site_end_offset', 'evidence_json',
|
|
71
73
|
'confidence', 'unresolved_reason', 'local_service_name',
|
|
@@ -79,6 +81,13 @@ const requiredColumns = {
|
|
|
79
81
|
'call_site_end_offset', 'call_role', 'status', 'evidence_json',
|
|
80
82
|
'confidence', 'unresolved_reason',
|
|
81
83
|
],
|
|
84
|
+
generated_constants: [
|
|
85
|
+
'id', 'repo_id', 'source_file', 'source_line', 'name',
|
|
86
|
+
'container_name', 'member_name', 'value', 'constant_kind', 'exported',
|
|
87
|
+
'stable', 'resolution_status', 'unresolved_reason',
|
|
88
|
+
'declaration_start_offset', 'declaration_end_offset',
|
|
89
|
+
'value_start_offset', 'value_end_offset',
|
|
90
|
+
],
|
|
82
91
|
graph_edges: [
|
|
83
92
|
'id', 'workspace_id', 'edge_type', 'status', 'from_kind', 'from_id',
|
|
84
93
|
'to_kind', 'to_id', 'confidence', 'evidence_json', 'is_dynamic',
|
|
@@ -167,6 +176,33 @@ function bindingIndexInvalid(
|
|
|
167
176
|
&& metadata?.unique === unique && metadata.partial === partial ? 0 : 1;
|
|
168
177
|
}
|
|
169
178
|
|
|
179
|
+
function tableIndexInvalid(
|
|
180
|
+
db: Db,
|
|
181
|
+
table: string,
|
|
182
|
+
name: string,
|
|
183
|
+
columns: readonly string[],
|
|
184
|
+
unique: number,
|
|
185
|
+
): number {
|
|
186
|
+
const metadata = db.prepare(`PRAGMA index_list(${table})`)
|
|
187
|
+
.all().find((item) => item.name === name);
|
|
188
|
+
return metadata?.unique === unique
|
|
189
|
+
&& indexColumns(db, name).join('\0') === columns.join('\0') ? 0 : 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function eventSurfaceIndexesInvalid(db: Db): number {
|
|
193
|
+
return tableIndexInvalid(
|
|
194
|
+
db, 'outbound_calls', 'idx_outbound_event_skeleton',
|
|
195
|
+
['event_skeleton_signature', 'call_type', 'repo_id'], 0,
|
|
196
|
+
) + tableIndexInvalid(
|
|
197
|
+
db, 'generated_constants', 'idx_generated_constant_name',
|
|
198
|
+
['repo_id', 'source_file', 'name'], 0,
|
|
199
|
+
) + tableIndexInvalid(
|
|
200
|
+
db, 'generated_constants', 'uq_generated_constant_site',
|
|
201
|
+
['repo_id', 'source_file', 'name', 'declaration_start_offset',
|
|
202
|
+
'declaration_end_offset'], 1,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
170
206
|
function exactBindingIndexInvalid(db: Db): number {
|
|
171
207
|
const row = db.prepare(`SELECT sql FROM sqlite_master
|
|
172
208
|
WHERE type='index' AND name='uq_service_binding_exact_site'`).get();
|
|
@@ -197,5 +233,9 @@ export function invalidSchemaStructureCategories(
|
|
|
197
233
|
exactBindingIndexInvalid(db)
|
|
198
234
|
+ bindingIndexInvalid(db, 'idx_service_binding_site', 0, 0),
|
|
199
235
|
),
|
|
236
|
+
...category(
|
|
237
|
+
'schema_event_surface_index_invalid',
|
|
238
|
+
eventSurfaceIndexesInvalid(db),
|
|
239
|
+
),
|
|
200
240
|
];
|
|
201
241
|
}
|
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 '{}', 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);
|
|
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, environment_declarations_json TEXT DEFAULT '{"schema":"service-flow/environment-declarations@1","allowedKeys":["SHARD_CODE"],"status":"not_applicable","reason":null,"recordCap":32,"total":0,"shown":0,"omitted":0,"declarations":[]}', 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);
|
|
@@ -10,7 +10,8 @@ CREATE TABLE IF NOT EXISTS handler_classes (id INTEGER PRIMARY KEY, repo_id INTE
|
|
|
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
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
|
-
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);
|
|
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, event_skeleton_signature TEXT, event_skeleton_json 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
|
+
CREATE TABLE IF NOT EXISTS generated_constants (id INTEGER PRIMARY KEY, repo_id INTEGER NOT NULL, source_file TEXT NOT NULL, source_line INTEGER NOT NULL, name TEXT NOT NULL, container_name TEXT, member_name TEXT, value TEXT, constant_kind TEXT NOT NULL, exported INTEGER NOT NULL, stable INTEGER NOT NULL, resolution_status TEXT NOT NULL CHECK(resolution_status IN ('resolved','refused')), unresolved_reason TEXT, declaration_start_offset INTEGER NOT NULL, declaration_end_offset INTEGER NOT NULL, value_start_offset INTEGER NOT NULL, value_end_offset INTEGER NOT NULL, FOREIGN KEY(repo_id) REFERENCES repositories(id) ON DELETE CASCADE);
|
|
14
15
|
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
16
|
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);
|
|
16
17
|
CREATE TABLE IF NOT EXISTS index_runs (id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, started_at TEXT NOT NULL, finished_at TEXT, status TEXT NOT NULL, repo_count INTEGER NOT NULL, file_count INTEGER NOT NULL, diagnostic_count INTEGER NOT NULL, error_message TEXT, owner_pid INTEGER, FOREIGN KEY(workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE);
|
|
@@ -24,6 +25,9 @@ CREATE INDEX IF NOT EXISTS idx_extension_import ON cds_services(extension_module
|
|
|
24
25
|
CREATE INDEX IF NOT EXISTS idx_operation_name ON cds_operations(operation_name, operation_path);
|
|
25
26
|
CREATE INDEX IF NOT EXISTS idx_operation_base ON cds_operations(base_operation_id);
|
|
26
27
|
CREATE INDEX IF NOT EXISTS idx_calls_repo ON outbound_calls(repo_id, call_type);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_outbound_event_skeleton ON outbound_calls(event_skeleton_signature,call_type,repo_id);
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_generated_constant_name ON generated_constants(repo_id,source_file,name);
|
|
30
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_generated_constant_site ON generated_constants(repo_id,source_file,name,declaration_start_offset,declaration_end_offset);
|
|
27
31
|
CREATE INDEX IF NOT EXISTS idx_symbol_calls_caller ON symbol_calls(repo_id, caller_symbol_id);
|
|
28
32
|
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
33
|
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);
|