@memberjunction/db-auto-doc 6.2.0-edge.4 → 6.2.0-edge.6
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/README.md +105 -0
- package/dist/api/DBAutoDocAPI.d.ts +2 -0
- package/dist/api/DBAutoDocAPI.d.ts.map +1 -1
- package/dist/api/DBAutoDocAPI.js +2 -1
- package/dist/api/DBAutoDocAPI.js.map +1 -1
- package/dist/commands/analyze.d.ts +1 -0
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +6 -0
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/export.d.ts +28 -19
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +91 -12
- package/dist/commands/export.js.map +1 -1
- package/dist/core/AnalysisEngine.d.ts +39 -6
- package/dist/core/AnalysisEngine.d.ts.map +1 -1
- package/dist/core/AnalysisEngine.js +144 -50
- package/dist/core/AnalysisEngine.js.map +1 -1
- package/dist/core/AnalysisOrchestrator.d.ts +20 -1
- package/dist/core/AnalysisOrchestrator.d.ts.map +1 -1
- package/dist/core/AnalysisOrchestrator.js +144 -28
- package/dist/core/AnalysisOrchestrator.js.map +1 -1
- package/dist/core/PKProposalGate.d.ts +22 -0
- package/dist/core/PKProposalGate.d.ts.map +1 -0
- package/dist/core/PKProposalGate.js +52 -0
- package/dist/core/PKProposalGate.js.map +1 -0
- package/dist/database/Database.d.ts +8 -0
- package/dist/database/Database.d.ts.map +1 -1
- package/dist/database/Database.js +23 -1
- package/dist/database/Database.js.map +1 -1
- package/dist/discovery/ColumnNormalizer.d.ts +31 -0
- package/dist/discovery/ColumnNormalizer.d.ts.map +1 -1
- package/dist/discovery/ColumnNormalizer.js +68 -2
- package/dist/discovery/ColumnNormalizer.js.map +1 -1
- package/dist/discovery/Composer.d.ts +33 -2
- package/dist/discovery/Composer.d.ts.map +1 -1
- package/dist/discovery/Composer.js +126 -5
- package/dist/discovery/Composer.js.map +1 -1
- package/dist/discovery/DiscoveryEngine.d.ts +3 -0
- package/dist/discovery/DiscoveryEngine.d.ts.map +1 -1
- package/dist/discovery/DiscoveryEngine.js +1 -1
- package/dist/discovery/DiscoveryEngine.js.map +1 -1
- package/dist/discovery/EmbeddingProvider.d.ts +6 -1
- package/dist/discovery/EmbeddingProvider.d.ts.map +1 -1
- package/dist/discovery/EmbeddingProvider.js +11 -1
- package/dist/discovery/EmbeddingProvider.js.map +1 -1
- package/dist/discovery/EnumCandidateGate.d.ts +19 -0
- package/dist/discovery/EnumCandidateGate.d.ts.map +1 -1
- package/dist/discovery/EnumCandidateGate.js +47 -5
- package/dist/discovery/EnumCandidateGate.js.map +1 -1
- package/dist/discovery/FKGraphWalker.d.ts +35 -0
- package/dist/discovery/FKGraphWalker.d.ts.map +1 -1
- package/dist/discovery/FKGraphWalker.js +113 -12
- package/dist/discovery/FKGraphWalker.js.map +1 -1
- package/dist/discovery/IndexKeyEvidence.d.ts +36 -0
- package/dist/discovery/IndexKeyEvidence.d.ts.map +1 -0
- package/dist/discovery/IndexKeyEvidence.js +42 -0
- package/dist/discovery/IndexKeyEvidence.js.map +1 -0
- package/dist/discovery/JoinProbe.d.ts +148 -0
- package/dist/discovery/JoinProbe.d.ts.map +1 -0
- package/dist/discovery/JoinProbe.js +233 -0
- package/dist/discovery/JoinProbe.js.map +1 -0
- package/dist/discovery/LLMDiscoveryValidator.d.ts +10 -1
- package/dist/discovery/LLMDiscoveryValidator.d.ts.map +1 -1
- package/dist/discovery/LLMDiscoveryValidator.js +44 -12
- package/dist/discovery/LLMDiscoveryValidator.js.map +1 -1
- package/dist/discovery/LLMSanityChecker.d.ts.map +1 -1
- package/dist/discovery/LLMSanityChecker.js +10 -1
- package/dist/discovery/LLMSanityChecker.js.map +1 -1
- package/dist/discovery/OrganicKeyDetector.d.ts +29 -1
- package/dist/discovery/OrganicKeyDetector.d.ts.map +1 -1
- package/dist/discovery/OrganicKeyDetector.js +34 -5
- package/dist/discovery/OrganicKeyDetector.js.map +1 -1
- package/dist/discovery/OrganicKeyTranslator.d.ts +24 -5
- package/dist/discovery/OrganicKeyTranslator.d.ts.map +1 -1
- package/dist/discovery/OrganicKeyTranslator.js +32 -14
- package/dist/discovery/OrganicKeyTranslator.js.map +1 -1
- package/dist/discovery/PKDetector.d.ts +9 -0
- package/dist/discovery/PKDetector.d.ts.map +1 -1
- package/dist/discovery/PKDetector.js +86 -8
- package/dist/discovery/PKDetector.js.map +1 -1
- package/dist/discovery/SemanticPhase.d.ts +25 -0
- package/dist/discovery/SemanticPhase.d.ts.map +1 -1
- package/dist/discovery/SemanticPhase.js +55 -11
- package/dist/discovery/SemanticPhase.js.map +1 -1
- package/dist/discovery/StructuralPhase.d.ts +27 -5
- package/dist/discovery/StructuralPhase.d.ts.map +1 -1
- package/dist/discovery/StructuralPhase.js +42 -9
- package/dist/discovery/StructuralPhase.js.map +1 -1
- package/dist/discovery/TransitiveBridgeDetector.d.ts +5 -1
- package/dist/discovery/TransitiveBridgeDetector.d.ts.map +1 -1
- package/dist/discovery/TransitiveBridgeDetector.js +25 -3
- package/dist/discovery/TransitiveBridgeDetector.js.map +1 -1
- package/dist/discovery/embedding-provider-default.d.ts +25 -0
- package/dist/discovery/embedding-provider-default.d.ts.map +1 -0
- package/dist/discovery/embedding-provider-default.js +47 -0
- package/dist/discovery/embedding-provider-default.js.map +1 -0
- package/dist/drivers/BaseAutoDocDriver.d.ts +63 -0
- package/dist/drivers/BaseAutoDocDriver.d.ts.map +1 -1
- package/dist/drivers/BaseAutoDocDriver.js +3 -5
- package/dist/drivers/BaseAutoDocDriver.js.map +1 -1
- package/dist/drivers/MySQLDriver.d.ts +21 -1
- package/dist/drivers/MySQLDriver.d.ts.map +1 -1
- package/dist/drivers/MySQLDriver.js +64 -0
- package/dist/drivers/MySQLDriver.js.map +1 -1
- package/dist/drivers/PostgreSQLDriver.d.ts +43 -2
- package/dist/drivers/PostgreSQLDriver.d.ts.map +1 -1
- package/dist/drivers/PostgreSQLDriver.js +132 -19
- package/dist/drivers/PostgreSQLDriver.js.map +1 -1
- package/dist/drivers/SQLServerDriver.d.ts +28 -1
- package/dist/drivers/SQLServerDriver.d.ts.map +1 -1
- package/dist/drivers/SQLServerDriver.js +108 -1
- package/dist/drivers/SQLServerDriver.js.map +1 -1
- package/dist/drivers/probeErrors.d.ts +30 -0
- package/dist/drivers/probeErrors.d.ts.map +1 -0
- package/dist/drivers/probeErrors.js +94 -0
- package/dist/drivers/probeErrors.js.map +1 -0
- package/dist/generators/AdditionalSchemaInfoGenerator.d.ts +34 -18
- package/dist/generators/AdditionalSchemaInfoGenerator.d.ts.map +1 -1
- package/dist/generators/AdditionalSchemaInfoGenerator.js +130 -8
- package/dist/generators/AdditionalSchemaInfoGenerator.js.map +1 -1
- package/dist/generators/ErdClusterGenerator.d.ts +44 -0
- package/dist/generators/ErdClusterGenerator.d.ts.map +1 -0
- package/dist/generators/ErdClusterGenerator.js +216 -0
- package/dist/generators/ErdClusterGenerator.js.map +1 -0
- package/dist/generators/ErdClusterer.d.ts +130 -0
- package/dist/generators/ErdClusterer.d.ts.map +1 -0
- package/dist/generators/ErdClusterer.js +308 -0
- package/dist/generators/ErdClusterer.js.map +1 -0
- package/dist/generators/HTMLGenerator.d.ts +6 -0
- package/dist/generators/HTMLGenerator.d.ts.map +1 -1
- package/dist/generators/HTMLGenerator.js +41 -6
- package/dist/generators/HTMLGenerator.js.map +1 -1
- package/dist/generators/MermaidGenerator.d.ts +3 -0
- package/dist/generators/MermaidGenerator.d.ts.map +1 -1
- package/dist/generators/MermaidGenerator.js +4 -0
- package/dist/generators/MermaidGenerator.js.map +1 -1
- package/dist/generators/index.d.ts +2 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/prompts/PromptEngine.d.ts.map +1 -1
- package/dist/prompts/PromptEngine.js +10 -1
- package/dist/prompts/PromptEngine.js.map +1 -1
- package/dist/types/analysis.d.ts +2 -0
- package/dist/types/analysis.d.ts.map +1 -1
- package/dist/types/config.d.ts +128 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +7 -1
- package/dist/types/config.js.map +1 -1
- package/dist/types/discovery.d.ts +41 -0
- package/dist/types/discovery.d.ts.map +1 -1
- package/dist/types/state.d.ts +29 -0
- package/dist/types/state.d.ts.map +1 -1
- package/dist/utils/call-deadline.d.ts +43 -0
- package/dist/utils/call-deadline.d.ts.map +1 -0
- package/dist/utils/call-deadline.js +81 -0
- package/dist/utils/call-deadline.js.map +1 -0
- package/dist/utils/config-loader.d.ts +5 -0
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +37 -0
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/index-hints.d.ts +43 -0
- package/dist/utils/index-hints.d.ts.map +1 -0
- package/dist/utils/index-hints.js +80 -0
- package/dist/utils/index-hints.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/llm-factory.d.ts +1 -0
- package/dist/utils/llm-factory.d.ts.map +1 -1
- package/dist/utils/llm-factory.js +6 -1
- package/dist/utils/llm-factory.js.map +1 -1
- package/dist/utils/seed-context.d.ts +23 -0
- package/dist/utils/seed-context.d.ts.map +1 -0
- package/dist/utils/seed-context.js +38 -0
- package/dist/utils/seed-context.js.map +1 -0
- package/package.json +5 -5
- package/prompts/fk-pruning-holistic.md +7 -0
- package/prompts/fk-pruning-table.md +20 -1
- package/prompts/pk-pruning-holistic.md +7 -0
- package/prompts/pk-pruning-table.md +17 -0
- package/prompts/table-analysis.md +11 -0
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
* Main analysis orchestrator
|
|
3
3
|
* Coordinates the entire documentation generation workflow
|
|
4
4
|
*/
|
|
5
|
-
import { DatabaseDocumentation, AnalysisRun, SchemaDefinition } from '../types/state.js';
|
|
5
|
+
import { DatabaseDocumentation, AnalysisRun, SchemaDefinition, TableDefinition } from '../types/state.js';
|
|
6
|
+
import type { BaseAutoDocDriver } from "../drivers/BaseAutoDocDriver.js";
|
|
6
7
|
import { TableNode, BackpropagationTrigger } from '../types/analysis.js';
|
|
8
|
+
import { KeyVerifier } from '../discovery/JoinProbe.js';
|
|
9
|
+
import { ColumnDescriptionPromptResult } from '../types/prompts.js';
|
|
7
10
|
import { DBAutoDocConfig } from '../types/config.js';
|
|
8
11
|
import { PromptEngine } from '../prompts/PromptEngine.js';
|
|
9
12
|
import { StateManager } from '../state/StateManager.js';
|
|
@@ -13,6 +16,8 @@ export declare class AnalysisEngine {
|
|
|
13
16
|
private promptEngine;
|
|
14
17
|
private stateManager;
|
|
15
18
|
private iterationTracker;
|
|
19
|
+
/** Database access for checks the LLM's proposals must pass (composite PK uniqueness). Optional: without it, composite PK proposals are rejected. */
|
|
20
|
+
private driver?;
|
|
16
21
|
private backpropagationEngine;
|
|
17
22
|
private convergenceDetector;
|
|
18
23
|
private guardrailsManager;
|
|
@@ -20,7 +25,22 @@ export declare class AnalysisEngine {
|
|
|
20
25
|
private startTime;
|
|
21
26
|
private currentRun?;
|
|
22
27
|
private onProgress;
|
|
23
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Verifies a proposed key against the data before it is confirmed. Optional: when
|
|
30
|
+
* absent, LLM-proposed FKs are still emitted but stamped `Unprobed` rather than
|
|
31
|
+
* silently presented as though someone had checked them.
|
|
32
|
+
*/
|
|
33
|
+
private keyVerifier;
|
|
34
|
+
/**
|
|
35
|
+
* Supply the probe used to verify LLM-proposed foreign keys. Injected rather than
|
|
36
|
+
* constructed here because the engine has no driver of its own; the orchestrator owns
|
|
37
|
+
* the connection and passes ONE verifier so the per-run probe budget is shared with
|
|
38
|
+
* the organic-key path instead of each path getting its own allowance.
|
|
39
|
+
*/
|
|
40
|
+
SetKeyVerifier(verifier: KeyVerifier | null): void;
|
|
41
|
+
constructor(config: DBAutoDocConfig, promptEngine: PromptEngine, stateManager: StateManager, iterationTracker: IterationTracker, onProgress?: (message: string, data?: Record<string, unknown>) => void,
|
|
42
|
+
/** Database access for checks the LLM's proposals must pass (composite PK uniqueness). Optional: without it, composite PK proposals are rejected. */
|
|
43
|
+
driver?: BaseAutoDocDriver);
|
|
24
44
|
/**
|
|
25
45
|
* Initialize timing for guardrails and set current run
|
|
26
46
|
*/
|
|
@@ -99,6 +119,8 @@ export declare class AnalysisEngine {
|
|
|
99
119
|
* Build context for table analysis
|
|
100
120
|
*/
|
|
101
121
|
private buildTableContext;
|
|
122
|
+
/** The table's existing indexes as prompt lines, or undefined when there are none to show. */
|
|
123
|
+
private indexHintLines;
|
|
102
124
|
/**
|
|
103
125
|
* Build FK candidate stats from the discovery phase for this table.
|
|
104
126
|
* Provides the LLM with cross-table relationship evidence (value overlap,
|
|
@@ -109,7 +131,7 @@ export declare class AnalysisEngine {
|
|
|
109
131
|
* Persist enum/value-list verdicts from LLM response onto ColumnDefinition.
|
|
110
132
|
* Keeps the highest-confidence verdict across multiple iterations.
|
|
111
133
|
*/
|
|
112
|
-
|
|
134
|
+
protected persistEnumVerdicts(table: TableDefinition, columnDescriptions: ColumnDescriptionPromptResult[] | undefined, modelUsed: string): void;
|
|
113
135
|
/**
|
|
114
136
|
* Sanitize LLM-returned enum values: trim whitespace, drop empties and null sentinels.
|
|
115
137
|
*/
|
|
@@ -177,9 +199,10 @@ export declare class AnalysisEngine {
|
|
|
177
199
|
*/
|
|
178
200
|
private extractAndFeedbackFKInsights;
|
|
179
201
|
/**
|
|
180
|
-
* Process PK proposal from LLM. The LLM can propose a PK, but
|
|
181
|
-
*
|
|
182
|
-
*
|
|
202
|
+
* Process PK proposal from LLM. The LLM can propose a PK, but it must pass deterministic
|
|
203
|
+
* eligibility: every column exists and has no nulls, and the key is unique — a single column
|
|
204
|
+
* by its own statistics, a composite key as a combination (checked in the database).
|
|
205
|
+
* If any check fails, the entire proposal is rejected.
|
|
183
206
|
*/
|
|
184
207
|
private processPKInsightFromLLM;
|
|
185
208
|
/**
|
|
@@ -188,6 +211,16 @@ export declare class AnalysisEngine {
|
|
|
188
211
|
* Uses the foreignKeys array from table-analysis prompt response instead of brittle regex parsing.
|
|
189
212
|
* Per architectural decision: use LLM for language understanding, deterministic code for processing.
|
|
190
213
|
*/
|
|
214
|
+
/**
|
|
215
|
+
* Probe an LLM-proposed foreign key.
|
|
216
|
+
*
|
|
217
|
+
* Returns `Unprobed` rather than throwing for every reason a probe can fail to run —
|
|
218
|
+
* no verifier injected, no such table or column in the analysed state, or the driver
|
|
219
|
+
* reporting that it could not evaluate the join. `Refuted` is reserved for a probe
|
|
220
|
+
* that RAN and measured containment below the floor, because only that is evidence
|
|
221
|
+
* the key is wrong.
|
|
222
|
+
*/
|
|
223
|
+
private verifyProposedFK;
|
|
191
224
|
private processFKInsightsFromLLM;
|
|
192
225
|
/**
|
|
193
226
|
* Find a column in the state schemas
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalysisEngine.d.ts","sourceRoot":"","sources":["../../src/core/AnalysisEngine.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"AnalysisEngine.d.ts","sourceRoot":"","sources":["../../src/core/AnalysisEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,17 +3,39 @@
|
|
|
3
3
|
* Coordinates the entire documentation generation workflow
|
|
4
4
|
*/
|
|
5
5
|
import { EnsureArray } from "../utils/ensureArray.js";
|
|
6
|
+
import { CheckPKProposal } from "./PKProposalGate.js";
|
|
7
|
+
import { BuildIndexHints, FormatIndexHint, IsUniquelyIndexed } from "../utils/index-hints.js";
|
|
8
|
+
import { SeedContextExcerpt } from "../utils/seed-context.js";
|
|
6
9
|
import { EnumCandidateGate } from '../discovery/EnumCandidateGate.js';
|
|
10
|
+
import { StampFor } from '../discovery/JoinProbe.js';
|
|
7
11
|
import { BackpropagationEngine } from './BackpropagationEngine.js';
|
|
8
12
|
import { ConvergenceDetector } from './ConvergenceDetector.js';
|
|
9
13
|
import { GuardrailsManager } from './GuardrailsManager.js';
|
|
10
14
|
export class AnalysisEngine {
|
|
11
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Supply the probe used to verify LLM-proposed foreign keys. Injected rather than
|
|
17
|
+
* constructed here because the engine has no driver of its own; the orchestrator owns
|
|
18
|
+
* the connection and passes ONE verifier so the per-run probe budget is shared with
|
|
19
|
+
* the organic-key path instead of each path getting its own allowance.
|
|
20
|
+
*/
|
|
21
|
+
SetKeyVerifier(verifier) {
|
|
22
|
+
this.keyVerifier = verifier;
|
|
23
|
+
}
|
|
24
|
+
constructor(config, promptEngine, stateManager, iterationTracker, onProgress,
|
|
25
|
+
/** Database access for checks the LLM's proposals must pass (composite PK uniqueness). Optional: without it, composite PK proposals are rejected. */
|
|
26
|
+
driver) {
|
|
12
27
|
this.config = config;
|
|
13
28
|
this.promptEngine = promptEngine;
|
|
14
29
|
this.stateManager = stateManager;
|
|
15
30
|
this.iterationTracker = iterationTracker;
|
|
31
|
+
this.driver = driver;
|
|
16
32
|
this.startTime = 0;
|
|
33
|
+
/**
|
|
34
|
+
* Verifies a proposed key against the data before it is confirmed. Optional: when
|
|
35
|
+
* absent, LLM-proposed FKs are still emitted but stamped `Unprobed` rather than
|
|
36
|
+
* silently presented as though someone had checked them.
|
|
37
|
+
*/
|
|
38
|
+
this.keyVerifier = null;
|
|
17
39
|
this.onProgress = onProgress || (() => { });
|
|
18
40
|
this.backpropagationEngine = new BackpropagationEngine(promptEngine, stateManager, iterationTracker, config.analysis.backpropagation.maxDepth, config);
|
|
19
41
|
this.convergenceDetector = new ConvergenceDetector(config.analysis.convergence, stateManager, iterationTracker);
|
|
@@ -128,7 +150,7 @@ export class AnalysisEngine {
|
|
|
128
150
|
const [schemaName, tableName] = tableKey.split('.');
|
|
129
151
|
const table = this.stateManager.findTable(state, schemaName, tableName);
|
|
130
152
|
const candidates = tablePKs.map(pk => ({ columns: pk.columnNames, confidence: pk.confidence, locked: pk.status === 'confirmed' }));
|
|
131
|
-
const context = { sourceSchema: schemaName, sourceTable: tableName, tableDescription: table?.description || '', allTables, candidates, seedContext: state.seedContext ?? this.config.seedContext };
|
|
153
|
+
const context = { sourceSchema: schemaName, sourceTable: tableName, tableDescription: table?.description || '', allTables, candidates, existingIndexes: this.indexHintLines(table), seedContext: state.seedContext ?? this.config.seedContext, seedExcerpt: SeedContextExcerpt(state.seedContext ?? this.config.seedContext, [tableName]) };
|
|
132
154
|
const result = await this.promptEngine.executePrompt('pk-pruning-table', context, { responseFormat: 'JSON', temperature: override?.temperature ?? 0.05, maxTokens: override?.maxTokens ?? this.config.ai.maxTokens, modelOverride: override?.model, effortLevelOverride: override?.effortLevel });
|
|
133
155
|
if (!result.success || !result.result) {
|
|
134
156
|
console.log(`[AnalysisEngine] PK pruning failed for ${tableKey}: ${result.errorMessage}`);
|
|
@@ -154,7 +176,7 @@ export class AnalysisEngine {
|
|
|
154
176
|
if (allProposals.length === 0)
|
|
155
177
|
return { removed: 0, kept: allPKs.length };
|
|
156
178
|
this.onProgress('PK pruning pass 2: holistic review', { proposals: allProposals.length, model: effectiveModel });
|
|
157
|
-
const holisticContext = { allTables, proposals: allProposals.map(p => ({ sourceSchema: p.sourceSchema, sourceTable: p.sourceTable, columns: p.columns, confidence: p.confidence, reasoning: p.reasoning })), seedContext: state.seedContext ?? this.config.seedContext };
|
|
179
|
+
const holisticContext = { allTables, proposals: allProposals.map(p => ({ sourceSchema: p.sourceSchema, sourceTable: p.sourceTable, columns: p.columns, confidence: p.confidence, reasoning: p.reasoning })), seedContext: state.seedContext ?? this.config.seedContext, seedExcerpt: SeedContextExcerpt(state.seedContext ?? this.config.seedContext, []) };
|
|
158
180
|
const holisticResult = await this.promptEngine.executePrompt('pk-pruning-holistic', holisticContext, { responseFormat: 'JSON', temperature: override?.temperature ?? 0.05, maxTokens: override?.maxTokens ?? this.config.ai.maxTokens, modelOverride: override?.model, effortLevelOverride: override?.effortLevel });
|
|
159
181
|
let removed = 0;
|
|
160
182
|
if (holisticResult.success && holisticResult.result) {
|
|
@@ -237,7 +259,8 @@ export class AnalysisEngine {
|
|
|
237
259
|
targetTable: fk.targetTable,
|
|
238
260
|
targetColumn: fk.targetColumn,
|
|
239
261
|
confidence: fk.confidence,
|
|
240
|
-
locked: fk.status === 'confirmed'
|
|
262
|
+
locked: fk.status === 'confirmed',
|
|
263
|
+
targetUniquelyIndexed: IsUniquelyIndexed(this.stateManager.findTable(state, fk.targetSchema, fk.targetTable), fk.targetColumn)
|
|
241
264
|
}));
|
|
242
265
|
const context = {
|
|
243
266
|
sourceSchema: schemaName,
|
|
@@ -245,7 +268,9 @@ export class AnalysisEngine {
|
|
|
245
268
|
tableDescription: table?.description || '',
|
|
246
269
|
allTables,
|
|
247
270
|
candidates,
|
|
248
|
-
|
|
271
|
+
existingIndexes: this.indexHintLines(table),
|
|
272
|
+
seedContext: state.seedContext ?? this.config.seedContext,
|
|
273
|
+
seedExcerpt: SeedContextExcerpt(state.seedContext ?? this.config.seedContext, [tableName])
|
|
249
274
|
};
|
|
250
275
|
const result = await this.promptEngine.executePrompt('fk-pruning-table', context, {
|
|
251
276
|
responseFormat: 'JSON',
|
|
@@ -304,7 +329,8 @@ export class AnalysisEngine {
|
|
|
304
329
|
confidence: p.confidence,
|
|
305
330
|
reasoning: p.reasoning
|
|
306
331
|
})),
|
|
307
|
-
seedContext: state.seedContext ?? this.config.seedContext
|
|
332
|
+
seedContext: state.seedContext ?? this.config.seedContext,
|
|
333
|
+
seedExcerpt: SeedContextExcerpt(state.seedContext ?? this.config.seedContext, [])
|
|
308
334
|
};
|
|
309
335
|
const holisticResult = await this.promptEngine.executePrompt('fk-pruning-holistic', holisticContext, {
|
|
310
336
|
responseFormat: 'JSON',
|
|
@@ -438,11 +464,11 @@ export class AnalysisEngine {
|
|
|
438
464
|
this.persistEnumVerdicts(table, result.result.columnDescriptions, run.modelUsed);
|
|
439
465
|
// Process structured FK insights from LLM and feed back to discovery phase
|
|
440
466
|
if (state.phases.keyDetection && result.result.foreignKeys) {
|
|
441
|
-
this.processFKInsightsFromLLM(state, tableNode.schema, tableNode.table, result.result.foreignKeys);
|
|
467
|
+
await this.processFKInsightsFromLLM(state, tableNode.schema, tableNode.table, result.result.foreignKeys);
|
|
442
468
|
}
|
|
443
469
|
// Process PK proposal from LLM — verify eligibility deterministically
|
|
444
470
|
if (state.phases.keyDetection && result.result.primaryKey) {
|
|
445
|
-
this.processPKInsightFromLLM(state, tableNode.schema, tableNode.table, result.result.primaryKey);
|
|
471
|
+
await this.processPKInsightFromLLM(state, tableNode.schema, tableNode.table, result.result.primaryKey);
|
|
446
472
|
}
|
|
447
473
|
// Update inferred business domain
|
|
448
474
|
if (result.result.inferredBusinessDomain) {
|
|
@@ -535,9 +561,15 @@ export class AnalysisEngine {
|
|
|
535
561
|
allTables,
|
|
536
562
|
groundTruth: groundTruthContext,
|
|
537
563
|
fkCandidateStats,
|
|
538
|
-
enumCandidates: enumCandidates.length > 0 ? enumCandidates : undefined
|
|
564
|
+
enumCandidates: enumCandidates.length > 0 ? enumCandidates : undefined,
|
|
565
|
+
existingIndexes: this.indexHintLines(table)
|
|
539
566
|
};
|
|
540
567
|
}
|
|
568
|
+
/** The table's existing indexes as prompt lines, or undefined when there are none to show. */
|
|
569
|
+
indexHintLines(table) {
|
|
570
|
+
const lines = BuildIndexHints(table).map(FormatIndexHint);
|
|
571
|
+
return lines.length > 0 ? lines : undefined;
|
|
572
|
+
}
|
|
541
573
|
/**
|
|
542
574
|
* Build FK candidate stats from the discovery phase for this table.
|
|
543
575
|
* Provides the LLM with cross-table relationship evidence (value overlap,
|
|
@@ -580,11 +612,16 @@ export class AnalysisEngine {
|
|
|
580
612
|
// If sanitization drops all values below 2, skip the verdict
|
|
581
613
|
if (sanitizedValues.length < 2 && vl.isEnum)
|
|
582
614
|
continue;
|
|
615
|
+
// The prompt asks for a verdict only on enum candidates; enforce that here, and keep only
|
|
616
|
+
// values observed in the column, so a verdict on a non-candidate or an invented value is dropped.
|
|
617
|
+
const keptValues = this.enumCandidateGate.FilterVerdictValues(column, table.rowCount, sanitizedValues);
|
|
618
|
+
if (keptValues === null && vl.isEnum)
|
|
619
|
+
continue;
|
|
583
620
|
const newVerdict = {
|
|
584
621
|
isEnum: vl.isEnum,
|
|
585
622
|
type: vl.type,
|
|
586
623
|
confidence: vl.confidence,
|
|
587
|
-
values: sanitizedValues,
|
|
624
|
+
values: keptValues ?? sanitizedValues,
|
|
588
625
|
reasoning: vl.reasoning,
|
|
589
626
|
source: 'llm',
|
|
590
627
|
cardinalityAtDecision: column.statistics
|
|
@@ -967,11 +1004,12 @@ export class AnalysisEngine {
|
|
|
967
1004
|
return;
|
|
968
1005
|
}
|
|
969
1006
|
/**
|
|
970
|
-
* Process PK proposal from LLM. The LLM can propose a PK, but
|
|
971
|
-
*
|
|
972
|
-
*
|
|
1007
|
+
* Process PK proposal from LLM. The LLM can propose a PK, but it must pass deterministic
|
|
1008
|
+
* eligibility: every column exists and has no nulls, and the key is unique — a single column
|
|
1009
|
+
* by its own statistics, a composite key as a combination (checked in the database).
|
|
1010
|
+
* If any check fails, the entire proposal is rejected.
|
|
973
1011
|
*/
|
|
974
|
-
processPKInsightFromLLM(state, schemaName, tableName, pkProposal) {
|
|
1012
|
+
async processPKInsightFromLLM(state, schemaName, tableName, pkProposal) {
|
|
975
1013
|
const discoveryPhase = state.phases.keyDetection;
|
|
976
1014
|
if (!discoveryPhase || !pkProposal || !pkProposal.columns || pkProposal.columns.length === 0)
|
|
977
1015
|
return;
|
|
@@ -1016,47 +1054,29 @@ export class AnalysisEngine {
|
|
|
1016
1054
|
}
|
|
1017
1055
|
return;
|
|
1018
1056
|
}
|
|
1019
|
-
// New PK proposal — verify
|
|
1057
|
+
// New PK proposal — verify it deterministically
|
|
1020
1058
|
const table = this.stateManager.findTable(state, schemaName, tableName);
|
|
1021
1059
|
if (!table)
|
|
1022
1060
|
return;
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
// Check PK eligibility from stats
|
|
1030
|
-
const stats = column.statistics;
|
|
1031
|
-
if (!stats) {
|
|
1032
|
-
console.log(`[AnalysisEngine] LLM PK rejected: ${schemaName}.${tableName}.${colName} — no statistics available`);
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
// Safety: prefer pre-computed uniquenessRatio if available (totalRows can be 0 due to field naming bug)
|
|
1036
|
-
const uniqueness = stats.uniquenessRatio != null && stats.uniquenessRatio > 0
|
|
1037
|
-
? stats.uniquenessRatio
|
|
1038
|
-
: (stats.totalRows > 0 ? stats.distinctCount / stats.totalRows : 0);
|
|
1039
|
-
const hasNulls = (stats.nullCount || 0) > 0;
|
|
1040
|
-
if (hasNulls) {
|
|
1041
|
-
console.log(`[AnalysisEngine] LLM PK rejected: ${schemaName}.${tableName}.${colName} — has ${stats.nullCount} nulls`);
|
|
1042
|
-
return;
|
|
1043
|
-
}
|
|
1044
|
-
if (uniqueness < 1.0) {
|
|
1045
|
-
console.log(`[AnalysisEngine] LLM PK rejected: ${schemaName}.${tableName}.${colName} — uniqueness ${(uniqueness * 100).toFixed(1)}% (must be 100%)`);
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1061
|
+
const sampleSize = this.config.analysis.relationshipDiscovery?.sampling?.maxRowsPerTable ?? 1000;
|
|
1062
|
+
const driver = this.driver;
|
|
1063
|
+
const verdict = await CheckPKProposal(table, columns, driver ? (cols) => driver.checkColumnCombinationUniqueness(schemaName, tableName, cols, sampleSize) : undefined);
|
|
1064
|
+
if (!verdict.Eligible) {
|
|
1065
|
+
console.log(`[AnalysisEngine] LLM PK rejected: ${schemaName}.${tableName} ${verdict.Reason}`);
|
|
1066
|
+
return;
|
|
1048
1067
|
}
|
|
1068
|
+
const pkColumns = verdict.Columns ?? columns;
|
|
1049
1069
|
// All columns pass — create new PK candidate
|
|
1050
1070
|
const newPK = {
|
|
1051
1071
|
schemaName,
|
|
1052
1072
|
tableName,
|
|
1053
|
-
columnNames:
|
|
1073
|
+
columnNames: pkColumns,
|
|
1054
1074
|
confidence,
|
|
1055
1075
|
evidence: {
|
|
1056
1076
|
uniqueness: 1.0,
|
|
1057
1077
|
nullCount: 0,
|
|
1058
1078
|
totalRows: table.rowCount || 0,
|
|
1059
|
-
dataPattern:
|
|
1079
|
+
dataPattern: pkColumns.length > 1 ? 'composite' : 'unknown',
|
|
1060
1080
|
namingScore: 0.5,
|
|
1061
1081
|
dataTypeScore: 0.8,
|
|
1062
1082
|
warnings: ['Created from LLM proposal — passed deterministic eligibility']
|
|
@@ -1067,12 +1087,12 @@ export class AnalysisEngine {
|
|
|
1067
1087
|
};
|
|
1068
1088
|
discoveryPhase.discovered.primaryKeys.push(newPK);
|
|
1069
1089
|
// Update column flags
|
|
1070
|
-
for (const colName of
|
|
1090
|
+
for (const colName of pkColumns) {
|
|
1071
1091
|
const column = this.findColumnInState(state, schemaName, tableName, colName);
|
|
1072
1092
|
if (column)
|
|
1073
1093
|
column.isPrimaryKey = true;
|
|
1074
1094
|
}
|
|
1075
|
-
console.log(`[AnalysisEngine] Created PK from LLM: ${schemaName}.${tableName} (${
|
|
1095
|
+
console.log(`[AnalysisEngine] Created PK from LLM: ${schemaName}.${tableName} (${pkColumns.join(', ')}) confidence: ${confidence}`);
|
|
1076
1096
|
}
|
|
1077
1097
|
/**
|
|
1078
1098
|
* Process structured FK insights from LLM and create feedback to discovery phase
|
|
@@ -1080,7 +1100,39 @@ export class AnalysisEngine {
|
|
|
1080
1100
|
* Uses the foreignKeys array from table-analysis prompt response instead of brittle regex parsing.
|
|
1081
1101
|
* Per architectural decision: use LLM for language understanding, deterministic code for processing.
|
|
1082
1102
|
*/
|
|
1083
|
-
|
|
1103
|
+
/**
|
|
1104
|
+
* Probe an LLM-proposed foreign key.
|
|
1105
|
+
*
|
|
1106
|
+
* Returns `Unprobed` rather than throwing for every reason a probe can fail to run —
|
|
1107
|
+
* no verifier injected, no such table or column in the analysed state, or the driver
|
|
1108
|
+
* reporting that it could not evaluate the join. `Refuted` is reserved for a probe
|
|
1109
|
+
* that RAN and measured containment below the floor, because only that is evidence
|
|
1110
|
+
* the key is wrong.
|
|
1111
|
+
*/
|
|
1112
|
+
async verifyProposedFK(childSchema, childTable, childColumn, parentSchema, parentTable, parentColumn) {
|
|
1113
|
+
const now = new Date().toISOString();
|
|
1114
|
+
if (!this.keyVerifier) {
|
|
1115
|
+
return {
|
|
1116
|
+
Status: 'Unprobed',
|
|
1117
|
+
Containment: null,
|
|
1118
|
+
Reason: 'no key verifier configured for this run',
|
|
1119
|
+
ProbedAt: now
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
if (!parentColumn || !parentTable || !parentSchema) {
|
|
1123
|
+
return {
|
|
1124
|
+
Status: 'Unprobed',
|
|
1125
|
+
Containment: null,
|
|
1126
|
+
Reason: 'LLM did not name a complete target column',
|
|
1127
|
+
ProbedAt: now
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
return this.keyVerifier.Verify({
|
|
1131
|
+
Child: { Schema: childSchema, Table: childTable, Column: childColumn },
|
|
1132
|
+
Parent: { Schema: parentSchema, Table: parentTable, Column: parentColumn }
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
async processFKInsightsFromLLM(state, schemaName, tableName, foreignKeys) {
|
|
1084
1136
|
const discoveryPhase = state.phases.keyDetection;
|
|
1085
1137
|
if (!discoveryPhase || !foreignKeys || foreignKeys.length === 0)
|
|
1086
1138
|
return;
|
|
@@ -1122,6 +1174,38 @@ export class AnalysisEngine {
|
|
|
1122
1174
|
console.log(`[AnalysisEngine] Rejecting LLM FK suggestion for confirmed PK: ${schemaName}.${tableName}.${columnName} -> ${referencesSchema}.${referencesTable}.${referencesColumn}`);
|
|
1123
1175
|
continue;
|
|
1124
1176
|
}
|
|
1177
|
+
// ── Verify the proposed join before anything is confirmed ────────────────
|
|
1178
|
+
//
|
|
1179
|
+
// This is the gate that was missing. The statistical path (FKDetector) probes
|
|
1180
|
+
// and rejects below 75% containment at its GATE 6; this path never probed at
|
|
1181
|
+
// all — it wrote a literal `valueOverlap: 0` placeholder, set
|
|
1182
|
+
// `status: 'confirmed'`, and stamped `column.isForeignKey = true`. The
|
|
1183
|
+
// additionalSchemaInfo generator then emits from BOTH
|
|
1184
|
+
// `discovered.foreignKeys` (gated on confidence + status, never on overlap)
|
|
1185
|
+
// and `column.foreignKeyReferences` (gated on nothing at all), so an
|
|
1186
|
+
// LLM-proposed key reached the output through two independent unchecked doors.
|
|
1187
|
+
//
|
|
1188
|
+
// The shape this catches: a connector schema whose parent key is namespaced
|
|
1189
|
+
// `<product>:<id>` while children store the bare id. "A foreign key points at
|
|
1190
|
+
// the parent's primary key" is correct almost everywhere and unfalsifiable
|
|
1191
|
+
// from schema shape, so the LLM proposes it for every child table and every
|
|
1192
|
+
// one of them matches zero rows.
|
|
1193
|
+
const probe = await this.verifyProposedFK(schemaName, tableName, columnName, referencesSchema, referencesTable, referencesColumn);
|
|
1194
|
+
if (probe.Status === 'Refuted') {
|
|
1195
|
+
// Measured, not assumed: the probe ran and the join matches (near) nothing.
|
|
1196
|
+
// Dropping it here is the only place it can be dropped — once the column is
|
|
1197
|
+
// stamped, the introspected-FK path emits it with no gate to fail. It runs
|
|
1198
|
+
// before the PK block below, so a refuted proposal is not evidence against a
|
|
1199
|
+
// PK candidate either.
|
|
1200
|
+
console.log(`[AnalysisEngine] Rejecting LLM FK — probe refuted: ${schemaName}.${tableName}.${columnName} -> ${referencesSchema}.${referencesTable}.${referencesColumn} (${probe.Reason})`);
|
|
1201
|
+
this.onProgress(`Rejected LLM-proposed FK ${schemaName}.${tableName}.${columnName} → ${referencesTable}.${referencesColumn}: ${probe.Reason}`, { refutedKey: `${schemaName}.${tableName}.${columnName}` });
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
// Verified or Unprobed both proceed. Unprobed proceeds deliberately: refusing
|
|
1205
|
+
// whenever the probe cannot run turns a permissions or timeout problem into
|
|
1206
|
+
// total key loss with no signal. It proceeds STAMPED, so a consumer can tell a
|
|
1207
|
+
// checked key from an unchecked one.
|
|
1208
|
+
const verificationStamp = StampFor('LLM', probe);
|
|
1125
1209
|
if (existingPK && existingPK.status !== 'confirmed') {
|
|
1126
1210
|
// PK exists but isn't confirmed — LLM suggesting it's an FK is evidence
|
|
1127
1211
|
// that the PK detection was wrong. Reject the PK.
|
|
@@ -1140,6 +1224,7 @@ export class AnalysisEngine {
|
|
|
1140
1224
|
existingFK.validatedByLLM = true;
|
|
1141
1225
|
existingFK.status = 'confirmed';
|
|
1142
1226
|
existingFK.confidence = Math.min(existingFK.confidence + 20, 100);
|
|
1227
|
+
existingFK.verification = verificationStamp;
|
|
1143
1228
|
feedback.type = 'confidence_change';
|
|
1144
1229
|
feedback.newConfidence = existingFK.confidence;
|
|
1145
1230
|
feedback.affectedCandidates.push(`FK:${schemaName}.${tableName}.${columnName}`);
|
|
@@ -1169,17 +1254,26 @@ export class AnalysisEngine {
|
|
|
1169
1254
|
confidence: Math.round(confidence * 100),
|
|
1170
1255
|
evidence: {
|
|
1171
1256
|
namingMatch: 0.9,
|
|
1172
|
-
|
|
1257
|
+
// Real measurements where the probe ran. These were hardcoded 0 / 0 / 0,
|
|
1258
|
+
// which is indistinguishable from a probe that ran and found nothing —
|
|
1259
|
+
// the exact conflation that let a schema of zero-match keys be emitted
|
|
1260
|
+
// with the same apparent confidence as a correct one.
|
|
1261
|
+
valueOverlap: probe.Containment ? probe.Containment.Containment : 0,
|
|
1173
1262
|
cardinalityRatio: 0,
|
|
1174
1263
|
dataTypeMatch: true,
|
|
1175
1264
|
nullPercentage: 0,
|
|
1176
|
-
sampleSize: 0,
|
|
1177
|
-
orphanCount:
|
|
1178
|
-
|
|
1265
|
+
sampleSize: probe.Containment ? probe.Containment.SampledValues : 0,
|
|
1266
|
+
orphanCount: probe.Containment
|
|
1267
|
+
? probe.Containment.SampledValues - probe.Containment.MatchedValues
|
|
1268
|
+
: 0,
|
|
1269
|
+
warnings: probe.Status === 'Unprobed'
|
|
1270
|
+
? ['Created from structured LLM output', `Join NOT verified: ${probe.Reason}`]
|
|
1271
|
+
: ['Created from structured LLM output', `Join verified: ${probe.Reason}`]
|
|
1179
1272
|
},
|
|
1180
1273
|
discoveredInIteration: 1,
|
|
1181
1274
|
validatedByLLM: true,
|
|
1182
|
-
status: 'confirmed'
|
|
1275
|
+
status: 'confirmed',
|
|
1276
|
+
verification: verificationStamp
|
|
1183
1277
|
};
|
|
1184
1278
|
discoveryPhase.discovered.foreignKeys.push(newFK);
|
|
1185
1279
|
feedback.newConfidence = newFK.confidence;
|