@duckcodeailabs/dql-agent 1.14.0 → 1.14.2
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/dist/agent-run-engine.d.ts +68 -2
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +520 -26
- package/dist/agent-run-engine.js.map +1 -1
- package/dist/agent-run-gates.d.ts.map +1 -1
- package/dist/agent-run-gates.js +25 -0
- package/dist/agent-run-gates.js.map +1 -1
- package/dist/agentic/analyst-loop.d.ts +91 -0
- package/dist/agentic/analyst-loop.d.ts.map +1 -0
- package/dist/agentic/analyst-loop.js +331 -0
- package/dist/agentic/analyst-loop.js.map +1 -0
- package/dist/agentic/assumptions.d.ts +62 -0
- package/dist/agentic/assumptions.d.ts.map +1 -0
- package/dist/agentic/assumptions.js +73 -0
- package/dist/agentic/assumptions.js.map +1 -0
- package/dist/agentic/business-explanation.d.ts +56 -0
- package/dist/agentic/business-explanation.d.ts.map +1 -0
- package/dist/agentic/business-explanation.js +130 -0
- package/dist/agentic/business-explanation.js.map +1 -0
- package/dist/agentic/identifier-ledger.d.ts +74 -0
- package/dist/agentic/identifier-ledger.d.ts.map +1 -0
- package/dist/agentic/identifier-ledger.js +151 -0
- package/dist/agentic/identifier-ledger.js.map +1 -0
- package/dist/agentic/ledger-tools.d.ts +68 -0
- package/dist/agentic/ledger-tools.d.ts.map +1 -0
- package/dist/agentic/ledger-tools.js +198 -0
- package/dist/agentic/ledger-tools.js.map +1 -0
- package/dist/agentic/orchestrator-policy.d.ts +60 -0
- package/dist/agentic/orchestrator-policy.d.ts.map +1 -0
- package/dist/agentic/orchestrator-policy.js +73 -0
- package/dist/agentic/orchestrator-policy.js.map +1 -0
- package/dist/agentic/orchestrator.d.ts +54 -0
- package/dist/agentic/orchestrator.d.ts.map +1 -0
- package/dist/agentic/orchestrator.js +67 -0
- package/dist/agentic/orchestrator.js.map +1 -0
- package/dist/agentic/preview-tool.d.ts +36 -0
- package/dist/agentic/preview-tool.d.ts.map +1 -0
- package/dist/agentic/preview-tool.js +71 -0
- package/dist/agentic/preview-tool.js.map +1 -0
- package/dist/agentic/rerank.d.ts +50 -0
- package/dist/agentic/rerank.d.ts.map +1 -0
- package/dist/agentic/rerank.js +140 -0
- package/dist/agentic/rerank.js.map +1 -0
- package/dist/agentic/research/hypothesis.d.ts +110 -0
- package/dist/agentic/research/hypothesis.d.ts.map +1 -0
- package/dist/agentic/research/hypothesis.js +125 -0
- package/dist/agentic/research/hypothesis.js.map +1 -0
- package/dist/agentic/research/synthesis.d.ts +36 -0
- package/dist/agentic/research/synthesis.d.ts.map +1 -0
- package/dist/agentic/research/synthesis.js +109 -0
- package/dist/agentic/research/synthesis.js.map +1 -0
- package/dist/agentic/research-agent.d.ts +54 -0
- package/dist/agentic/research-agent.d.ts.map +1 -0
- package/dist/agentic/research-agent.js +177 -0
- package/dist/agentic/research-agent.js.map +1 -0
- package/dist/agentic/sql-authorization.d.ts +131 -0
- package/dist/agentic/sql-authorization.d.ts.map +1 -0
- package/dist/agentic/sql-authorization.js +460 -0
- package/dist/agentic/sql-authorization.js.map +1 -0
- package/dist/agentic/tool-loop.d.ts +29 -0
- package/dist/agentic/tool-loop.d.ts.map +1 -1
- package/dist/agentic/tool-loop.js +129 -52
- package/dist/agentic/tool-loop.js.map +1 -1
- package/dist/agentic/toolset.d.ts.map +1 -1
- package/dist/agentic/toolset.js +155 -0
- package/dist/agentic/toolset.js.map +1 -1
- package/dist/agentic/turn-plan.d.ts +35 -0
- package/dist/agentic/turn-plan.d.ts.map +1 -0
- package/dist/agentic/turn-plan.js +108 -0
- package/dist/agentic/turn-plan.js.map +1 -0
- package/dist/agentic/value-probe.d.ts +89 -0
- package/dist/agentic/value-probe.d.ts.map +1 -0
- package/dist/agentic/value-probe.js +304 -0
- package/dist/agentic/value-probe.js.map +1 -0
- package/dist/analytical-frame.d.ts.map +1 -1
- package/dist/analytical-frame.js +10 -1
- package/dist/analytical-frame.js.map +1 -1
- package/dist/analytical-orchestration.d.ts +382 -0
- package/dist/analytical-orchestration.d.ts.map +1 -1
- package/dist/analytical-orchestration.js +782 -6
- package/dist/analytical-orchestration.js.map +1 -1
- package/dist/analytical-request-policy.d.ts +26 -0
- package/dist/analytical-request-policy.d.ts.map +1 -0
- package/dist/analytical-request-policy.js +82 -0
- package/dist/analytical-request-policy.js.map +1 -0
- package/dist/analytical-result-facts.d.ts +13 -0
- package/dist/analytical-result-facts.d.ts.map +1 -1
- package/dist/analytical-result-facts.js +37 -1
- package/dist/analytical-result-facts.js.map +1 -1
- package/dist/answer-loop.d.ts +105 -16
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +833 -108
- package/dist/answer-loop.js.map +1 -1
- package/dist/answer-shape.d.ts +32 -1
- package/dist/answer-shape.d.ts.map +1 -1
- package/dist/answer-shape.js +49 -3
- package/dist/answer-shape.js.map +1 -1
- package/dist/cascade/budgets.d.ts.map +1 -1
- package/dist/cascade/budgets.js +8 -1
- package/dist/cascade/budgets.js.map +1 -1
- package/dist/cascade/packer.d.ts +88 -0
- package/dist/cascade/packer.d.ts.map +1 -0
- package/dist/cascade/packer.js +106 -0
- package/dist/cascade/packer.js.map +1 -0
- package/dist/conversation/result-ops.d.ts +18 -3
- package/dist/conversation/result-ops.d.ts.map +1 -1
- package/dist/conversation/result-ops.js +77 -5
- package/dist/conversation/result-ops.js.map +1 -1
- package/dist/conversation/rolling-summary.d.ts.map +1 -1
- package/dist/conversation/rolling-summary.js +7 -2
- package/dist/conversation/rolling-summary.js.map +1 -1
- package/dist/conversation/session-store.d.ts +3 -0
- package/dist/conversation/session-store.d.ts.map +1 -1
- package/dist/conversation/session-store.js +8 -4
- package/dist/conversation/session-store.js.map +1 -1
- package/dist/conversation/snapshot.d.ts +20 -0
- package/dist/conversation/snapshot.d.ts.map +1 -1
- package/dist/conversation/snapshot.js +40 -2
- package/dist/conversation/snapshot.js.map +1 -1
- package/dist/fixtures/ask-ai-office-shaped.d.ts +153 -0
- package/dist/fixtures/ask-ai-office-shaped.d.ts.map +1 -0
- package/dist/fixtures/ask-ai-office-shaped.js +94 -0
- package/dist/fixtures/ask-ai-office-shaped.js.map +1 -0
- package/dist/hints/store.d.ts.map +1 -1
- package/dist/hints/store.js +15 -5
- package/dist/hints/store.js.map +1 -1
- package/dist/index.d.ts +29 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -6
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +36 -0
- package/dist/intent-controller.d.ts.map +1 -1
- package/dist/intent-controller.js +95 -0
- package/dist/intent-controller.js.map +1 -1
- package/dist/meaning-resolution.d.ts +69 -2
- package/dist/meaning-resolution.d.ts.map +1 -1
- package/dist/meaning-resolution.js +128 -7
- package/dist/meaning-resolution.js.map +1 -1
- package/dist/metadata/analysis-planner.d.ts.map +1 -1
- package/dist/metadata/analysis-planner.js +124 -7
- package/dist/metadata/analysis-planner.js.map +1 -1
- package/dist/metadata/block-fit.d.ts +36 -0
- package/dist/metadata/block-fit.d.ts.map +1 -1
- package/dist/metadata/block-fit.js +290 -38
- package/dist/metadata/block-fit.js.map +1 -1
- package/dist/metadata/catalog.d.ts +20 -23
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +193 -22
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/meaning-evidence.d.ts +10 -1
- package/dist/metadata/meaning-evidence.d.ts.map +1 -1
- package/dist/metadata/meaning-evidence.js +275 -53
- package/dist/metadata/meaning-evidence.js.map +1 -1
- package/dist/metadata/metric-match.d.ts +32 -0
- package/dist/metadata/metric-match.d.ts.map +1 -1
- package/dist/metadata/metric-match.js +120 -9
- package/dist/metadata/metric-match.js.map +1 -1
- package/dist/research-loop.d.ts +33 -2
- package/dist/research-loop.d.ts.map +1 -1
- package/dist/research-loop.js +66 -4
- package/dist/research-loop.js.map +1 -1
- package/dist/resolved-analytical-plan.d.ts +7 -0
- package/dist/resolved-analytical-plan.d.ts.map +1 -1
- package/dist/resolved-analytical-plan.js +11 -4
- package/dist/resolved-analytical-plan.js.map +1 -1
- package/dist/router.d.ts +32 -19
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +1977 -73
- package/dist/router.js.map +1 -1
- package/dist/semantic-bridge/member-select.d.ts.map +1 -1
- package/dist/semantic-bridge/member-select.js +39 -21
- package/dist/semantic-bridge/member-select.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Answering "what does this mean?" from governed metadata, without SQL.
|
|
3
|
+
*
|
|
4
|
+
* A definitional question — "what does the top_customers block measure?", "how
|
|
5
|
+
* is revenue defined here?" — is routed to `converse` today, which replies
|
|
6
|
+
* conversationally with NO governed evidence attached. So the product knows the
|
|
7
|
+
* answer (it has the description, the owner, the grain, the outputs, the
|
|
8
|
+
* certification status) and does not use it, which is the shape of the
|
|
9
|
+
* complaint that Ask AI cannot explain business context.
|
|
10
|
+
*
|
|
11
|
+
* This composes the explanation deterministically from what the catalog already
|
|
12
|
+
* holds. No provider, no warehouse: the facts are all in the object, and a model
|
|
13
|
+
* paraphrasing them can only add drift.
|
|
14
|
+
*/
|
|
15
|
+
const CERTIFIED_STATUSES = new Set(['certified', 'approved']);
|
|
16
|
+
function stringField(payload, key) {
|
|
17
|
+
const value = payload?.[key];
|
|
18
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
19
|
+
}
|
|
20
|
+
function stringList(payload, key) {
|
|
21
|
+
const value = payload?.[key];
|
|
22
|
+
if (!Array.isArray(value))
|
|
23
|
+
return [];
|
|
24
|
+
return value
|
|
25
|
+
.map((entry) => {
|
|
26
|
+
if (typeof entry === 'string')
|
|
27
|
+
return entry;
|
|
28
|
+
if (entry && typeof entry === 'object') {
|
|
29
|
+
const name = entry.name;
|
|
30
|
+
return typeof name === 'string' ? name : '';
|
|
31
|
+
}
|
|
32
|
+
return '';
|
|
33
|
+
})
|
|
34
|
+
.filter((entry) => Boolean(entry));
|
|
35
|
+
}
|
|
36
|
+
/** How a reader should weigh this object, in plain words. */
|
|
37
|
+
function trustSentence(object) {
|
|
38
|
+
const status = object.status?.toLowerCase() ?? '';
|
|
39
|
+
if (CERTIFIED_STATUSES.has(status)) {
|
|
40
|
+
return object.owner
|
|
41
|
+
? `It is certified and owned by ${object.owner}.`
|
|
42
|
+
: 'It is certified.';
|
|
43
|
+
}
|
|
44
|
+
if (status === 'draft')
|
|
45
|
+
return 'It is a draft — not certified, so treat the numbers as provisional.';
|
|
46
|
+
if (object.objectType.startsWith('dbt:') || object.objectType.startsWith('dbt_')) {
|
|
47
|
+
return 'This comes from the dbt project rather than a certified DQL block, so it describes the model, not an approved business definition.';
|
|
48
|
+
}
|
|
49
|
+
return status ? `Status: ${status}.` : 'It has no certification status recorded.';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Compose an explanation of one governed object.
|
|
53
|
+
*
|
|
54
|
+
* Returns `null` when there is nothing beyond a bare name to say. A confident
|
|
55
|
+
* paragraph assembled from an empty description is worse than falling through
|
|
56
|
+
* to the normal cascade: it reads like an answer and carries no information.
|
|
57
|
+
*/
|
|
58
|
+
export function explainObject(object) {
|
|
59
|
+
const parts = [];
|
|
60
|
+
const label = object.fullName ?? object.name;
|
|
61
|
+
const description = object.description?.trim();
|
|
62
|
+
const guidance = stringField(object.payload, 'llmContext');
|
|
63
|
+
if (!description && !guidance)
|
|
64
|
+
return null;
|
|
65
|
+
parts.push(`**${label}** — ${description ?? guidance}`);
|
|
66
|
+
const grain = stringField(object.payload, 'grain');
|
|
67
|
+
if (grain)
|
|
68
|
+
parts.push(`Reported at ${grain} grain.`);
|
|
69
|
+
const outputs = stringList(object.payload, 'declaredOutputs');
|
|
70
|
+
const resolvedOutputs = outputs.length > 0 ? outputs : stringList(object.payload, 'outputs');
|
|
71
|
+
if (resolvedOutputs.length > 0)
|
|
72
|
+
parts.push(`Returns: ${resolvedOutputs.join(', ')}.`);
|
|
73
|
+
const dimensions = stringList(object.payload, 'dimensions');
|
|
74
|
+
if (dimensions.length > 0)
|
|
75
|
+
parts.push(`Can be broken down by: ${dimensions.join(', ')}.`);
|
|
76
|
+
// Authored guidance is the one field written FOR a reader deciding whether to
|
|
77
|
+
// use this, so it earns its own line when a description already led.
|
|
78
|
+
if (description && guidance && guidance !== description)
|
|
79
|
+
parts.push(`When to use it: ${guidance}`);
|
|
80
|
+
if (object.domain)
|
|
81
|
+
parts.push(`Domain: ${object.domain}.`);
|
|
82
|
+
parts.push(trustSentence(object));
|
|
83
|
+
return {
|
|
84
|
+
text: parts.join('\n\n'),
|
|
85
|
+
citations: [object.objectKey],
|
|
86
|
+
governed: CERTIFIED_STATUSES.has(object.status?.toLowerCase() ?? ''),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Pick the object a definitional question is about, then explain it.
|
|
91
|
+
*
|
|
92
|
+
* Matching is by NAME MENTION, not relevance rank: "what does top_customers
|
|
93
|
+
* measure?" is asking about a specific artifact, and answering about a merely
|
|
94
|
+
* similar one would be a different question confidently answered. When nothing
|
|
95
|
+
* is named, return null and let the cascade run — guessing the subject of a
|
|
96
|
+
* definition is exactly the failure this is meant to avoid.
|
|
97
|
+
*/
|
|
98
|
+
export function composeBusinessExplanation(question, objects) {
|
|
99
|
+
const lower = question.toLowerCase();
|
|
100
|
+
const named = objects.filter((object) => {
|
|
101
|
+
for (const candidate of [object.name, object.fullName]) {
|
|
102
|
+
if (!candidate)
|
|
103
|
+
continue;
|
|
104
|
+
const bare = candidate.toLowerCase();
|
|
105
|
+
if (bare.length < 3)
|
|
106
|
+
continue;
|
|
107
|
+
if (lower.includes(bare))
|
|
108
|
+
return true;
|
|
109
|
+
const spaced = bare.replace(/[_.]+/g, ' ');
|
|
110
|
+
if (spaced.length > 3 && lower.includes(spaced))
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
});
|
|
115
|
+
if (named.length === 0)
|
|
116
|
+
return null;
|
|
117
|
+
// Prefer the most specific mention, then the most trustworthy artifact: a
|
|
118
|
+
// certified block explains a concept better than the raw dbt model beneath it.
|
|
119
|
+
const best = [...named].sort((left, right) => {
|
|
120
|
+
const leftName = (left.fullName ?? left.name).length;
|
|
121
|
+
const rightName = (right.fullName ?? right.name).length;
|
|
122
|
+
const leftCertified = CERTIFIED_STATUSES.has(left.status?.toLowerCase() ?? '') ? 1 : 0;
|
|
123
|
+
const rightCertified = CERTIFIED_STATUSES.has(right.status?.toLowerCase() ?? '') ? 1 : 0;
|
|
124
|
+
return rightCertified - leftCertified
|
|
125
|
+
|| rightName - leftName
|
|
126
|
+
|| left.objectKey.localeCompare(right.objectKey);
|
|
127
|
+
})[0];
|
|
128
|
+
return explainObject(best);
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=business-explanation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"business-explanation.js","sourceRoot":"","sources":["../../src/agentic/business-explanation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The identifier ledger — provenance for every name that reaches executed SQL.
|
|
3
|
+
*
|
|
4
|
+
* A semantic-layer-mediated agent reached 94.15% on Spider2-snow where frontier
|
|
5
|
+
* models score 17–21% unaided, and the rule doing most of that work is not a
|
|
6
|
+
* better prompt: only identifiers LIFTED FROM A COMPILER OUTPUT may appear in
|
|
7
|
+
* executed SQL. Everything else is a guess wearing a plausible name.
|
|
8
|
+
*
|
|
9
|
+
* The static equivalent already exists — `validateSqlAgainstLocalContext` checks
|
|
10
|
+
* SQL against the context pack's allowed relations. The ledger is its dynamic
|
|
11
|
+
* half: inside a loop the set of PROVEN identifiers grows with every tool
|
|
12
|
+
* observation, so what counts as admissible at step 5 is not what it was at
|
|
13
|
+
* step 1.
|
|
14
|
+
*
|
|
15
|
+
* Division of labour, deliberately: the validator EXTRACTS references from SQL
|
|
16
|
+
* (it owns the dialect handling, CTE scoping, and alias resolution); the ledger
|
|
17
|
+
* only adjudicates where each reference came from. Re-implementing extraction
|
|
18
|
+
* here would fork a parser that has already absorbed a lot of hard-won cases.
|
|
19
|
+
*/
|
|
20
|
+
/** Where an identifier was proven. Ordered loosely by strength of evidence. */
|
|
21
|
+
export type IdentifierSource = 'compiler' | 'preview' | 'schema_tool' | 'catalog';
|
|
22
|
+
export interface AdmittedIdentifier {
|
|
23
|
+
identifier: string;
|
|
24
|
+
source: IdentifierSource;
|
|
25
|
+
/** The tool call / receipt that proves it, for the audit trail. */
|
|
26
|
+
receiptId: string;
|
|
27
|
+
}
|
|
28
|
+
export interface LedgerAdjudication {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
/** References that no observation admitted. */
|
|
31
|
+
unadmitted: string[];
|
|
32
|
+
/**
|
|
33
|
+
* Closest admitted identifier per unadmitted reference. The dominant failure
|
|
34
|
+
* mode is a plausible-but-wrong column name, so handing the model the exact
|
|
35
|
+
* correction is worth far more than telling it that something was wrong.
|
|
36
|
+
*/
|
|
37
|
+
nearest: Record<string, string>;
|
|
38
|
+
}
|
|
39
|
+
/** Damerau-Levenshtein distance, for "did you mean" corrections. */
|
|
40
|
+
export declare function editDistance(left: string, right: string): number;
|
|
41
|
+
export declare class IdentifierLedger {
|
|
42
|
+
private readonly admitted;
|
|
43
|
+
/**
|
|
44
|
+
* Record identifiers proven by one observation.
|
|
45
|
+
*
|
|
46
|
+
* Admission is append-only and always carries a receipt: an identifier with no
|
|
47
|
+
* traceable source is exactly what this class exists to keep out of SQL.
|
|
48
|
+
*/
|
|
49
|
+
admit(source: IdentifierSource, identifiers: readonly string[], receiptId: string): void;
|
|
50
|
+
isAdmitted(identifier: string, options?: {
|
|
51
|
+
requireObserved?: boolean;
|
|
52
|
+
}): boolean;
|
|
53
|
+
size(): number;
|
|
54
|
+
entries(): AdmittedIdentifier[];
|
|
55
|
+
/** The closest admitted identifier, when one is close enough to suggest. */
|
|
56
|
+
nearestAdmitted(identifier: string): string | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Adjudicate the references a validator extracted from candidate SQL.
|
|
59
|
+
*
|
|
60
|
+
* Returns a report, never a throw: an unadmitted identifier is a correctable
|
|
61
|
+
* observation to feed back into the loop, not a terminal refusal. That
|
|
62
|
+
* distinction is the whole point — the old pipeline turned this into a
|
|
63
|
+
* `grounding_gap` and stopped.
|
|
64
|
+
*/
|
|
65
|
+
adjudicate(references: {
|
|
66
|
+
relations?: readonly string[];
|
|
67
|
+
columns?: readonly string[];
|
|
68
|
+
}, options?: {
|
|
69
|
+
requireObserved?: boolean;
|
|
70
|
+
}): LedgerAdjudication;
|
|
71
|
+
/** One line of feedback the model can act on directly. */
|
|
72
|
+
static renderCorrection(adjudication: LedgerAdjudication): string | undefined;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=identifier-ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identifier-ledger.d.ts","sourceRoot":"","sources":["../../src/agentic/identifier-ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,+EAA+E;AAC/E,MAAM,MAAM,gBAAgB,GACxB,UAAU,GACV,SAAS,GACT,aAAa,GACb,SAAS,CAAC;AAEd,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,gBAAgB,CAAC;IACzB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,+CAA+C;IAC/C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAmBD,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBhE;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyC;IAElE;;;;;OAKG;IACH,KAAK,CAAC,MAAM,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAaxF,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO;IAMpF,IAAI,IAAI,MAAM;IAId,OAAO,IAAI,kBAAkB,EAAE;IAI/B,4EAA4E;IAC5E,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAcvD;;;;;;;OAOG;IACH,UAAU,CACR,UAAU,EAAE;QAAE,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAC1E,OAAO,GAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAO,GAC1C,kBAAkB;IAarB,0DAA0D;IAC1D,MAAM,CAAC,gBAAgB,CAAC,YAAY,EAAE,kBAAkB,GAAG,MAAM,GAAG,SAAS;CAS9E"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The identifier ledger — provenance for every name that reaches executed SQL.
|
|
3
|
+
*
|
|
4
|
+
* A semantic-layer-mediated agent reached 94.15% on Spider2-snow where frontier
|
|
5
|
+
* models score 17–21% unaided, and the rule doing most of that work is not a
|
|
6
|
+
* better prompt: only identifiers LIFTED FROM A COMPILER OUTPUT may appear in
|
|
7
|
+
* executed SQL. Everything else is a guess wearing a plausible name.
|
|
8
|
+
*
|
|
9
|
+
* The static equivalent already exists — `validateSqlAgainstLocalContext` checks
|
|
10
|
+
* SQL against the context pack's allowed relations. The ledger is its dynamic
|
|
11
|
+
* half: inside a loop the set of PROVEN identifiers grows with every tool
|
|
12
|
+
* observation, so what counts as admissible at step 5 is not what it was at
|
|
13
|
+
* step 1.
|
|
14
|
+
*
|
|
15
|
+
* Division of labour, deliberately: the validator EXTRACTS references from SQL
|
|
16
|
+
* (it owns the dialect handling, CTE scoping, and alias resolution); the ledger
|
|
17
|
+
* only adjudicates where each reference came from. Re-implementing extraction
|
|
18
|
+
* here would fork a parser that has already absorbed a lot of hard-won cases.
|
|
19
|
+
*/
|
|
20
|
+
/** Case-insensitive qualified identity. Qualifiers remain part of authority. */
|
|
21
|
+
function normalizeIdentifier(value) {
|
|
22
|
+
return value
|
|
23
|
+
.trim()
|
|
24
|
+
.split('.')
|
|
25
|
+
.map((part) => part.trim().replace(/^["`\[]|["`\]]$/g, '').toLowerCase())
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join('.');
|
|
28
|
+
}
|
|
29
|
+
/** Last segment of a dotted identifier (`db.schema.orders` → `orders`). */
|
|
30
|
+
function leafOf(value) {
|
|
31
|
+
const normalized = normalizeIdentifier(value);
|
|
32
|
+
const parts = normalized.split('.').filter(Boolean);
|
|
33
|
+
return parts[parts.length - 1] ?? normalized;
|
|
34
|
+
}
|
|
35
|
+
/** Damerau-Levenshtein distance, for "did you mean" corrections. */
|
|
36
|
+
export function editDistance(left, right) {
|
|
37
|
+
if (left === right)
|
|
38
|
+
return 0;
|
|
39
|
+
const rows = left.length + 1;
|
|
40
|
+
const cols = right.length + 1;
|
|
41
|
+
const grid = Array.from({ length: rows }, () => new Array(cols).fill(0));
|
|
42
|
+
for (let i = 0; i < rows; i += 1)
|
|
43
|
+
grid[i][0] = i;
|
|
44
|
+
for (let j = 0; j < cols; j += 1)
|
|
45
|
+
grid[0][j] = j;
|
|
46
|
+
for (let i = 1; i < rows; i += 1) {
|
|
47
|
+
for (let j = 1; j < cols; j += 1) {
|
|
48
|
+
const cost = left[i - 1] === right[j - 1] ? 0 : 1;
|
|
49
|
+
let best = Math.min(grid[i - 1][j] + 1, grid[i][j - 1] + 1, grid[i - 1][j - 1] + cost);
|
|
50
|
+
if (i > 1 && j > 1 && left[i - 1] === right[j - 2] && left[i - 2] === right[j - 1]) {
|
|
51
|
+
best = Math.min(best, grid[i - 2][j - 2] + 1);
|
|
52
|
+
}
|
|
53
|
+
grid[i][j] = best;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return grid[rows - 1][cols - 1];
|
|
57
|
+
}
|
|
58
|
+
export class IdentifierLedger {
|
|
59
|
+
admitted = new Map();
|
|
60
|
+
/**
|
|
61
|
+
* Record identifiers proven by one observation.
|
|
62
|
+
*
|
|
63
|
+
* Admission is append-only and always carries a receipt: an identifier with no
|
|
64
|
+
* traceable source is exactly what this class exists to keep out of SQL.
|
|
65
|
+
*/
|
|
66
|
+
admit(source, identifiers, receiptId) {
|
|
67
|
+
for (const raw of identifiers) {
|
|
68
|
+
if (!raw || typeof raw !== 'string')
|
|
69
|
+
continue;
|
|
70
|
+
const key = normalizeIdentifier(raw);
|
|
71
|
+
if (!key)
|
|
72
|
+
continue;
|
|
73
|
+
const existing = this.admitted.get(key);
|
|
74
|
+
// Keep the strongest provenance rather than the most recent one.
|
|
75
|
+
if (!existing || sourceRank(source) > sourceRank(existing.source)) {
|
|
76
|
+
this.admitted.set(key, { identifier: raw, source, receiptId });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
isAdmitted(identifier, options = {}) {
|
|
81
|
+
const key = normalizeIdentifier(identifier);
|
|
82
|
+
const entry = this.admitted.get(key);
|
|
83
|
+
return Boolean(entry && (!options.requireObserved || entry.source !== 'catalog'));
|
|
84
|
+
}
|
|
85
|
+
size() {
|
|
86
|
+
return this.admitted.size;
|
|
87
|
+
}
|
|
88
|
+
entries() {
|
|
89
|
+
return [...this.admitted.values()];
|
|
90
|
+
}
|
|
91
|
+
/** The closest admitted identifier, when one is close enough to suggest. */
|
|
92
|
+
nearestAdmitted(identifier) {
|
|
93
|
+
const needle = leafOf(identifier);
|
|
94
|
+
let best;
|
|
95
|
+
for (const entry of this.admitted.values()) {
|
|
96
|
+
const distance = editDistance(needle, leafOf(entry.identifier));
|
|
97
|
+
if (!best || distance < best.distance)
|
|
98
|
+
best = { identifier: entry.identifier, distance };
|
|
99
|
+
}
|
|
100
|
+
if (!best)
|
|
101
|
+
return undefined;
|
|
102
|
+
// Only suggest a genuine near-miss. A distant "correction" reads as a
|
|
103
|
+
// confident wrong answer and sends the model further off course.
|
|
104
|
+
const tolerance = Math.max(2, Math.floor(needle.length / 3));
|
|
105
|
+
return best.distance <= tolerance ? best.identifier : undefined;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Adjudicate the references a validator extracted from candidate SQL.
|
|
109
|
+
*
|
|
110
|
+
* Returns a report, never a throw: an unadmitted identifier is a correctable
|
|
111
|
+
* observation to feed back into the loop, not a terminal refusal. That
|
|
112
|
+
* distinction is the whole point — the old pipeline turned this into a
|
|
113
|
+
* `grounding_gap` and stopped.
|
|
114
|
+
*/
|
|
115
|
+
adjudicate(references, options = {}) {
|
|
116
|
+
const unadmitted = [];
|
|
117
|
+
const nearest = {};
|
|
118
|
+
for (const reference of [...(references.relations ?? []), ...(references.columns ?? [])]) {
|
|
119
|
+
if (!reference || this.isAdmitted(reference, options))
|
|
120
|
+
continue;
|
|
121
|
+
if (unadmitted.includes(reference))
|
|
122
|
+
continue;
|
|
123
|
+
unadmitted.push(reference);
|
|
124
|
+
const suggestion = this.nearestAdmitted(reference);
|
|
125
|
+
if (suggestion)
|
|
126
|
+
nearest[reference] = suggestion;
|
|
127
|
+
}
|
|
128
|
+
return { ok: unadmitted.length === 0, unadmitted, nearest };
|
|
129
|
+
}
|
|
130
|
+
/** One line of feedback the model can act on directly. */
|
|
131
|
+
static renderCorrection(adjudication) {
|
|
132
|
+
if (adjudication.ok)
|
|
133
|
+
return undefined;
|
|
134
|
+
const parts = adjudication.unadmitted.map((reference) => {
|
|
135
|
+
const suggestion = adjudication.nearest[reference];
|
|
136
|
+
return suggestion ? `${reference} (did you mean ${suggestion}?)` : reference;
|
|
137
|
+
});
|
|
138
|
+
return `These identifiers were never returned by a tool, so they cannot be executed: ${parts.join(', ')}. `
|
|
139
|
+
+ 'Use only names that appeared in a compile, preview, or schema result.';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function sourceRank(source) {
|
|
143
|
+
switch (source) {
|
|
144
|
+
case 'compiler': return 4;
|
|
145
|
+
case 'preview': return 3;
|
|
146
|
+
case 'schema_tool': return 2;
|
|
147
|
+
case 'catalog': return 1;
|
|
148
|
+
default: return 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=identifier-ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identifier-ledger.js","sourceRoot":"","sources":["../../src/agentic/identifier-ledger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ledger enforcement over a tool loop.
|
|
3
|
+
*
|
|
4
|
+
* The rule that carries most of the 94.15%-on-Spider2 result is not a better
|
|
5
|
+
* prompt: only identifiers LIFTED FROM A TOOL OUTPUT may appear in executed SQL.
|
|
6
|
+
* Prompting for it does not work — a model asked to "only use real columns" will
|
|
7
|
+
* still invent `customer_tier` when the schema says `service_tier`, because the
|
|
8
|
+
* invented name is more plausible than the real one.
|
|
9
|
+
*
|
|
10
|
+
* So it is enforced structurally. Every tool result is harvested into an
|
|
11
|
+
* {@link IdentifierLedger} as the loop runs, and the SQL the model finally
|
|
12
|
+
* proposes is adjudicated against what was actually observed. A violation comes
|
|
13
|
+
* back as a correction naming the nearest observed identifier — not as a
|
|
14
|
+
* refusal, which is what the old pipeline turned it into.
|
|
15
|
+
*/
|
|
16
|
+
import type { AgentToolDefinition } from '../providers/types.js';
|
|
17
|
+
import { IdentifierLedger, type IdentifierSource } from './identifier-ledger.js';
|
|
18
|
+
export declare function evidenceSourceForTool(toolName: string): IdentifierSource;
|
|
19
|
+
/**
|
|
20
|
+
* Pull identifiers out of an arbitrary tool result.
|
|
21
|
+
*
|
|
22
|
+
* Deliberately structural rather than a schema per tool: the tool set grows, and
|
|
23
|
+
* a harvester that silently misses a new tool's output would quietly re-open the
|
|
24
|
+
* hole this class exists to close — every unharvested name becomes an
|
|
25
|
+
* "unadmitted" false alarm on correct SQL.
|
|
26
|
+
*
|
|
27
|
+
* Bounded in depth and count so a large preview cannot turn one observation into
|
|
28
|
+
* a pathological walk.
|
|
29
|
+
*/
|
|
30
|
+
export declare function harvestIdentifiers(output: unknown, limit?: number): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Wrap tools so every observation feeds the ledger.
|
|
33
|
+
*
|
|
34
|
+
* The wrapper is transparent: it returns the tool's own output untouched and
|
|
35
|
+
* never fails the call. A harvesting bug must not break a working tool — the
|
|
36
|
+
* worst case is a name that goes unadmitted and surfaces as a correction, which
|
|
37
|
+
* the loop can recover from.
|
|
38
|
+
*/
|
|
39
|
+
export declare function withLedgerHarvest(tools: readonly AgentToolDefinition[], ledger: IdentifierLedger, onObservation?: (event: {
|
|
40
|
+
tool: string;
|
|
41
|
+
admitted: number;
|
|
42
|
+
}) => void): AgentToolDefinition[];
|
|
43
|
+
/**
|
|
44
|
+
* The explore-then-compose discipline, as a system message.
|
|
45
|
+
*
|
|
46
|
+
* Mirrors the two phases that produced the 94% result: establish real names
|
|
47
|
+
* first, then assemble over only those names.
|
|
48
|
+
*/
|
|
49
|
+
export declare const ANALYST_TOOL_POLICY: string;
|
|
50
|
+
/**
|
|
51
|
+
* Adjudicate proposed SQL against what the loop actually observed.
|
|
52
|
+
*
|
|
53
|
+
* Returns a correction to feed back, or `undefined` when everything checks out.
|
|
54
|
+
* Never throws and never refuses: an unadmitted identifier is a fixable mistake,
|
|
55
|
+
* and the old pipeline turning it into a terminal `grounding_gap` is precisely
|
|
56
|
+
* the dead end being removed.
|
|
57
|
+
*/
|
|
58
|
+
export declare function adjudicateProposedSql(ledger: IdentifierLedger, references: {
|
|
59
|
+
relations?: readonly string[];
|
|
60
|
+
columns?: readonly string[];
|
|
61
|
+
}): {
|
|
62
|
+
ok: true;
|
|
63
|
+
} | {
|
|
64
|
+
ok: false;
|
|
65
|
+
correction: string;
|
|
66
|
+
unadmitted: string[];
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=ledger-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-tools.d.ts","sourceRoot":"","sources":["../../src/agentic/ledger-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAyBjF,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAExE;AAcD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,SAAM,GAAG,MAAM,EAAE,CAsEzE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,MAAM,EAAE,gBAAgB,EACxB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,GAClE,mBAAmB,EAAE,CAevB;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,QAgBpB,CAAC;AAEb;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE;IAAE,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACzE;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAWxE"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { IdentifierLedger } from './identifier-ledger.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where each tool's output sits on the evidence scale.
|
|
4
|
+
*
|
|
5
|
+
* A compiler emitting SQL is the strongest claim that a name exists; a catalog
|
|
6
|
+
* search only says something was INDEXED under that name, which is a weaker
|
|
7
|
+
* thing entirely — a stale index still returns rows.
|
|
8
|
+
*/
|
|
9
|
+
const TOOL_EVIDENCE = {
|
|
10
|
+
compile_semantic_query: 'compiler',
|
|
11
|
+
compile_governed_query: 'compiler',
|
|
12
|
+
preview_query: 'preview',
|
|
13
|
+
sample_notebook_dataset: 'preview',
|
|
14
|
+
execute_local_analysis: 'preview',
|
|
15
|
+
get_table_schema: 'schema_tool',
|
|
16
|
+
describe_notebook_dataset: 'schema_tool',
|
|
17
|
+
inspect_metadata_context: 'schema_tool',
|
|
18
|
+
list_notebook_datasets: 'schema_tool',
|
|
19
|
+
scan_manifest: 'catalog',
|
|
20
|
+
search_semantic_layer: 'catalog',
|
|
21
|
+
search_metadata: 'catalog',
|
|
22
|
+
check_compatibility: 'catalog',
|
|
23
|
+
};
|
|
24
|
+
export function evidenceSourceForTool(toolName) {
|
|
25
|
+
return TOOL_EVIDENCE[toolName] ?? 'catalog';
|
|
26
|
+
}
|
|
27
|
+
/** Keys whose STRING values name a relation or column. */
|
|
28
|
+
const IDENTIFIER_KEYS = new Set([
|
|
29
|
+
'relation', 'table', 'tableName', 'fullName', 'qualifiedName', 'name',
|
|
30
|
+
'column', 'columnName', 'dimension', 'metric', 'measure', 'resolvedName',
|
|
31
|
+
]);
|
|
32
|
+
/** Keys whose ARRAY values are lists of identifiers or column records. */
|
|
33
|
+
const IDENTIFIER_LIST_KEYS = new Set([
|
|
34
|
+
'columns', 'relations', 'tables', 'dimensions', 'metrics', 'measures',
|
|
35
|
+
'compatibleDimensions', 'outputs', 'groupBy', 'selectedColumns',
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* Pull identifiers out of an arbitrary tool result.
|
|
39
|
+
*
|
|
40
|
+
* Deliberately structural rather than a schema per tool: the tool set grows, and
|
|
41
|
+
* a harvester that silently misses a new tool's output would quietly re-open the
|
|
42
|
+
* hole this class exists to close — every unharvested name becomes an
|
|
43
|
+
* "unadmitted" false alarm on correct SQL.
|
|
44
|
+
*
|
|
45
|
+
* Bounded in depth and count so a large preview cannot turn one observation into
|
|
46
|
+
* a pathological walk.
|
|
47
|
+
*/
|
|
48
|
+
export function harvestIdentifiers(output, limit = 500) {
|
|
49
|
+
const found = [];
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
const push = (value) => {
|
|
52
|
+
if (typeof value !== 'string')
|
|
53
|
+
return;
|
|
54
|
+
const trimmed = value.trim();
|
|
55
|
+
// A SQL string or a sentence is not an identifier. Bare-ish names only.
|
|
56
|
+
if (!trimmed || trimmed.length > 128 || /\s/.test(trimmed))
|
|
57
|
+
return;
|
|
58
|
+
if (seen.has(trimmed) || found.length >= limit)
|
|
59
|
+
return;
|
|
60
|
+
seen.add(trimmed);
|
|
61
|
+
found.push(trimmed);
|
|
62
|
+
};
|
|
63
|
+
const walk = (node, depth) => {
|
|
64
|
+
if (found.length >= limit || depth > 6 || node === null || node === undefined)
|
|
65
|
+
return;
|
|
66
|
+
if (Array.isArray(node)) {
|
|
67
|
+
for (const entry of node) {
|
|
68
|
+
// A bare string inside an identifier list is itself an identifier.
|
|
69
|
+
if (typeof entry === 'string')
|
|
70
|
+
push(entry);
|
|
71
|
+
else
|
|
72
|
+
walk(entry, depth + 1);
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (typeof node !== 'object')
|
|
77
|
+
return;
|
|
78
|
+
const record = node;
|
|
79
|
+
// Schema/preview tools commonly return `{ relation, columns: [{ name }] }`.
|
|
80
|
+
// Preserve that ownership in the ledger. A bare `id` is not an execution
|
|
81
|
+
// identity: it could belong to any relation in the same result.
|
|
82
|
+
const relation = [record.relation, record.table, record.fullName, record.qualifiedName]
|
|
83
|
+
.find((value) => typeof value === 'string' && value.trim().length > 0)
|
|
84
|
+
?.trim();
|
|
85
|
+
const ownedColumnKeys = new Set(['columns', 'selectedColumns', 'outputs']);
|
|
86
|
+
if (relation) {
|
|
87
|
+
for (const key of ['columns', 'selectedColumns', 'outputs']) {
|
|
88
|
+
const values = record[key];
|
|
89
|
+
if (!Array.isArray(values))
|
|
90
|
+
continue;
|
|
91
|
+
for (const entry of values) {
|
|
92
|
+
const column = typeof entry === 'string'
|
|
93
|
+
? entry
|
|
94
|
+
: entry && typeof entry === 'object'
|
|
95
|
+
? [
|
|
96
|
+
entry.column,
|
|
97
|
+
entry.name,
|
|
98
|
+
entry.columnName,
|
|
99
|
+
].find((value) => typeof value === 'string')
|
|
100
|
+
: undefined;
|
|
101
|
+
if (column && !/\s/.test(column))
|
|
102
|
+
push(`${relation}.${column}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const [key, value] of Object.entries(record)) {
|
|
107
|
+
if (IDENTIFIER_KEYS.has(key))
|
|
108
|
+
push(value);
|
|
109
|
+
if (IDENTIFIER_LIST_KEYS.has(key) && Array.isArray(value)) {
|
|
110
|
+
// Relation-shaped schema and preview payloads were already harvested
|
|
111
|
+
// above as `relation.column`. Re-admitting their bare column leaves
|
|
112
|
+
// would let one table authorize the same leaf on another table.
|
|
113
|
+
if (relation && ownedColumnKeys.has(key))
|
|
114
|
+
continue;
|
|
115
|
+
for (const entry of value) {
|
|
116
|
+
if (typeof entry === 'string')
|
|
117
|
+
push(entry);
|
|
118
|
+
else
|
|
119
|
+
walk(entry, depth + 1);
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
walk(value, depth + 1);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
walk(output, 0);
|
|
127
|
+
return found;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Wrap tools so every observation feeds the ledger.
|
|
131
|
+
*
|
|
132
|
+
* The wrapper is transparent: it returns the tool's own output untouched and
|
|
133
|
+
* never fails the call. A harvesting bug must not break a working tool — the
|
|
134
|
+
* worst case is a name that goes unadmitted and surfaces as a correction, which
|
|
135
|
+
* the loop can recover from.
|
|
136
|
+
*/
|
|
137
|
+
export function withLedgerHarvest(tools, ledger, onObservation) {
|
|
138
|
+
return tools.map((tool, index) => ({
|
|
139
|
+
...tool,
|
|
140
|
+
run: async (args) => {
|
|
141
|
+
const output = await tool.run(args);
|
|
142
|
+
try {
|
|
143
|
+
const identifiers = harvestIdentifiers(output);
|
|
144
|
+
ledger.admit(evidenceSourceForTool(tool.name), identifiers, `${tool.name}#${index}`);
|
|
145
|
+
onObservation?.({ tool: tool.name, admitted: identifiers.length });
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Never let harvesting break a tool that worked.
|
|
149
|
+
}
|
|
150
|
+
return output;
|
|
151
|
+
},
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The explore-then-compose discipline, as a system message.
|
|
156
|
+
*
|
|
157
|
+
* Mirrors the two phases that produced the 94% result: establish real names
|
|
158
|
+
* first, then assemble over only those names.
|
|
159
|
+
*/
|
|
160
|
+
export const ANALYST_TOOL_POLICY = [
|
|
161
|
+
'Work in two phases.',
|
|
162
|
+
'',
|
|
163
|
+
'EXPLORE — establish what actually exists before writing any SQL:',
|
|
164
|
+
' · search for the governed metrics and dimensions the question needs',
|
|
165
|
+
' · check compatibility before assuming a breakdown is reachable; when it is not,',
|
|
166
|
+
' the tool tells you which dimensions ARE reachable — use one of those rather',
|
|
167
|
+
' than stopping',
|
|
168
|
+
' · compile a governed query and read back the identifiers it returns',
|
|
169
|
+
'',
|
|
170
|
+
'COMPOSE — assemble the final query using ONLY names a tool returned to you.',
|
|
171
|
+
'Every table and column in executed SQL must have appeared in a compile, preview,',
|
|
172
|
+
'or schema result. Preserve the qualified relation.column identity returned by the tool;',
|
|
173
|
+
'a bare leaf is not enough when multiple relations can contain the same name. A name that merely looks plausible is the single most common',
|
|
174
|
+
'way these queries go wrong: if you need a column you have not seen, inspect it',
|
|
175
|
+
'first rather than guessing its spelling.',
|
|
176
|
+
].join('\n');
|
|
177
|
+
/**
|
|
178
|
+
* Adjudicate proposed SQL against what the loop actually observed.
|
|
179
|
+
*
|
|
180
|
+
* Returns a correction to feed back, or `undefined` when everything checks out.
|
|
181
|
+
* Never throws and never refuses: an unadmitted identifier is a fixable mistake,
|
|
182
|
+
* and the old pipeline turning it into a terminal `grounding_gap` is precisely
|
|
183
|
+
* the dead end being removed.
|
|
184
|
+
*/
|
|
185
|
+
export function adjudicateProposedSql(ledger, references) {
|
|
186
|
+
// Catalog retrieval narrows tool search but is never execution evidence. A
|
|
187
|
+
// generated statement must name identifiers a compiler, preview, or schema
|
|
188
|
+
// observation actually returned during this invocation.
|
|
189
|
+
const verdict = ledger.adjudicate(references, { requireObserved: true });
|
|
190
|
+
if (verdict.ok)
|
|
191
|
+
return { ok: true };
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
unadmitted: verdict.unadmitted,
|
|
195
|
+
correction: IdentifierLedger.renderCorrection(verdict) ?? 'Some identifiers were never observed.',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=ledger-tools.js.map
|