@duckcodeailabs/dql-agent 1.14.2 → 1.14.4
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 +239 -4
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +2924 -85
- 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 +51 -3
- package/dist/agent-run-gates.js.map +1 -1
- package/dist/agent-run-planner.d.ts +6 -0
- package/dist/agent-run-planner.d.ts.map +1 -1
- package/dist/agent-run-planner.js +2 -0
- package/dist/agent-run-planner.js.map +1 -1
- package/dist/agent-run-store.d.ts.map +1 -1
- package/dist/agent-run-store.js +591 -11
- package/dist/agent-run-store.js.map +1 -1
- package/dist/agentic/analyst-loop.d.ts +9 -1
- package/dist/agentic/analyst-loop.d.ts.map +1 -1
- package/dist/agentic/analyst-loop.js +204 -4
- package/dist/agentic/analyst-loop.js.map +1 -1
- package/dist/agentic/assumptions.d.ts +1 -1
- package/dist/agentic/assumptions.d.ts.map +1 -1
- package/dist/agentic/assumptions.js.map +1 -1
- package/dist/agentic/ledger-tools.d.ts.map +1 -1
- package/dist/agentic/ledger-tools.js +8 -1
- package/dist/agentic/ledger-tools.js.map +1 -1
- package/dist/agentic/orchestrator-policy.d.ts +3 -1
- package/dist/agentic/orchestrator-policy.d.ts.map +1 -1
- package/dist/agentic/orchestrator-policy.js +1 -1
- package/dist/agentic/orchestrator-policy.js.map +1 -1
- package/dist/agentic/research-agent.d.ts.map +1 -1
- package/dist/agentic/research-agent.js +10 -1
- package/dist/agentic/research-agent.js.map +1 -1
- package/dist/agentic/sql-authorization.d.ts +8 -0
- package/dist/agentic/sql-authorization.d.ts.map +1 -1
- package/dist/agentic/sql-authorization.js +3 -0
- package/dist/agentic/sql-authorization.js.map +1 -1
- package/dist/agentic/tool-loop.d.ts +13 -1
- package/dist/agentic/tool-loop.d.ts.map +1 -1
- package/dist/agentic/tool-loop.js +441 -32
- package/dist/agentic/tool-loop.js.map +1 -1
- package/dist/analytical-compatibility.d.ts.map +1 -1
- package/dist/analytical-compatibility.js +8 -1
- package/dist/analytical-compatibility.js.map +1 -1
- package/dist/analytical-failure-repair.d.ts.map +1 -1
- package/dist/analytical-failure-repair.js +6 -2
- package/dist/analytical-failure-repair.js.map +1 -1
- package/dist/analytical-frame.d.ts +42 -1
- package/dist/analytical-frame.d.ts.map +1 -1
- package/dist/analytical-frame.js +490 -26
- package/dist/analytical-frame.js.map +1 -1
- package/dist/analytical-orchestration.d.ts +1259 -9
- package/dist/analytical-orchestration.d.ts.map +1 -1
- package/dist/analytical-orchestration.js +970 -84
- package/dist/analytical-orchestration.js.map +1 -1
- package/dist/answer-loop.d.ts +230 -10
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +1182 -85
- package/dist/answer-loop.js.map +1 -1
- package/dist/answer-shape.d.ts +27 -0
- package/dist/answer-shape.d.ts.map +1 -1
- package/dist/answer-shape.js +55 -0
- package/dist/answer-shape.js.map +1 -1
- package/dist/ask-observability/index.d.ts +11 -0
- package/dist/ask-observability/index.d.ts.map +1 -0
- package/dist/ask-observability/index.js +7 -0
- package/dist/ask-observability/index.js.map +1 -0
- package/dist/ask-observability/instrumentation.d.ts +35 -0
- package/dist/ask-observability/instrumentation.d.ts.map +1 -0
- package/dist/ask-observability/instrumentation.js +523 -0
- package/dist/ask-observability/instrumentation.js.map +1 -0
- package/dist/ask-observability/observer.d.ts +62 -0
- package/dist/ask-observability/observer.d.ts.map +1 -0
- package/dist/ask-observability/observer.js +537 -0
- package/dist/ask-observability/observer.js.map +1 -0
- package/dist/ask-observability/portable.d.ts +98 -0
- package/dist/ask-observability/portable.d.ts.map +1 -0
- package/dist/ask-observability/portable.js +1089 -0
- package/dist/ask-observability/portable.js.map +1 -0
- package/dist/ask-observability/store.d.ts +130 -0
- package/dist/ask-observability/store.d.ts.map +1 -0
- package/dist/ask-observability/store.js +1114 -0
- package/dist/ask-observability/store.js.map +1 -0
- package/dist/ask-observability/types.d.ts +380 -0
- package/dist/ask-observability/types.d.ts.map +1 -0
- package/dist/ask-observability/types.js +2 -0
- package/dist/ask-observability/types.js.map +1 -0
- package/dist/ask-observability/utils.d.ts +21 -0
- package/dist/ask-observability/utils.d.ts.map +1 -0
- package/dist/ask-observability/utils.js +102 -0
- package/dist/ask-observability/utils.js.map +1 -0
- package/dist/ask-runtime/ask-agent-runtime-v2.d.ts +850 -0
- package/dist/ask-runtime/ask-agent-runtime-v2.d.ts.map +1 -0
- package/dist/ask-runtime/ask-agent-runtime-v2.js +1716 -0
- package/dist/ask-runtime/ask-agent-runtime-v2.js.map +1 -0
- package/dist/ask-runtime/ask-analyst-runtime.d.ts +95 -0
- package/dist/ask-runtime/ask-analyst-runtime.d.ts.map +1 -0
- package/dist/ask-runtime/ask-analyst-runtime.js +8280 -0
- package/dist/ask-runtime/ask-analyst-runtime.js.map +1 -0
- package/dist/conversation/session-store.d.ts +31 -0
- package/dist/conversation/session-store.d.ts.map +1 -1
- package/dist/conversation/session-store.js +38 -0
- package/dist/conversation/session-store.js.map +1 -1
- package/dist/conversation/snapshot.d.ts +4 -1
- package/dist/conversation/snapshot.d.ts.map +1 -1
- package/dist/conversation/snapshot.js +85 -3
- package/dist/conversation/snapshot.js.map +1 -1
- package/dist/conversation/turn-trust.d.ts.map +1 -1
- package/dist/conversation/turn-trust.js +2 -0
- package/dist/conversation/turn-trust.js.map +1 -1
- package/dist/embeddings/provider.d.ts +1 -0
- package/dist/embeddings/provider.d.ts.map +1 -1
- package/dist/embeddings/provider.js +18 -0
- package/dist/embeddings/provider.js.map +1 -1
- package/dist/generated-analytical-proposal.d.ts +37 -1
- package/dist/generated-analytical-proposal.d.ts.map +1 -1
- package/dist/generated-analytical-proposal.js +246 -3
- package/dist/generated-analytical-proposal.js.map +1 -1
- package/dist/governed-relational-compiler.d.ts +15 -0
- package/dist/governed-relational-compiler.d.ts.map +1 -1
- package/dist/governed-relational-compiler.js +99 -1
- package/dist/governed-relational-compiler.js.map +1 -1
- package/dist/index.d.ts +21 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -7
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +87 -1
- package/dist/intent-controller.d.ts.map +1 -1
- package/dist/intent-controller.js +2 -2
- package/dist/intent-controller.js.map +1 -1
- package/dist/kg/build.js +32 -2
- package/dist/kg/build.js.map +1 -1
- package/dist/meaning-resolution.d.ts +207 -6
- package/dist/meaning-resolution.d.ts.map +1 -1
- package/dist/meaning-resolution.js +439 -53
- package/dist/meaning-resolution.js.map +1 -1
- package/dist/metadata/analysis-planner.d.ts.map +1 -1
- package/dist/metadata/analysis-planner.js +78 -7
- package/dist/metadata/analysis-planner.js.map +1 -1
- package/dist/metadata/catalog.d.ts +14 -0
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +29 -2
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/meaning-evidence.d.ts +28 -2
- package/dist/metadata/meaning-evidence.d.ts.map +1 -1
- package/dist/metadata/meaning-evidence.js +373 -16
- package/dist/metadata/meaning-evidence.js.map +1 -1
- package/dist/plan-execution-adapter.d.ts +9 -1
- package/dist/plan-execution-adapter.d.ts.map +1 -1
- package/dist/plan-execution-adapter.js +93 -13
- package/dist/plan-execution-adapter.js.map +1 -1
- package/dist/provider-egress.d.ts +28 -11
- package/dist/provider-egress.d.ts.map +1 -1
- package/dist/provider-egress.js +72 -22
- package/dist/provider-egress.js.map +1 -1
- package/dist/providers/claude.d.ts +2 -2
- package/dist/providers/claude.d.ts.map +1 -1
- package/dist/providers/claude.js +201 -37
- package/dist/providers/claude.js.map +1 -1
- package/dist/providers/dispatch.d.ts +27 -1
- package/dist/providers/dispatch.d.ts.map +1 -1
- package/dist/providers/dispatch.js +63 -1
- package/dist/providers/dispatch.js.map +1 -1
- package/dist/providers/gemini.js +10 -10
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/index.d.ts +2 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/ollama.js +15 -15
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.d.ts +2 -2
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +213 -43
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/types.d.ts +131 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/relationship-proof.d.ts +81 -0
- package/dist/relationship-proof.d.ts.map +1 -1
- package/dist/relationship-proof.js +228 -4
- package/dist/relationship-proof.js.map +1 -1
- package/dist/requirement-clauses.d.ts +23 -0
- package/dist/requirement-clauses.d.ts.map +1 -0
- package/dist/requirement-clauses.js +70 -0
- package/dist/requirement-clauses.js.map +1 -0
- package/dist/resolved-analytical-plan.d.ts +18 -4
- package/dist/resolved-analytical-plan.d.ts.map +1 -1
- package/dist/resolved-analytical-plan.js +953 -50
- package/dist/resolved-analytical-plan.js.map +1 -1
- package/dist/router.d.ts +118 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +2481 -232
- package/dist/router.js.map +1 -1
- package/dist/semantic-bridge/compose.d.ts.map +1 -1
- package/dist/semantic-bridge/compose.js +23 -4
- package/dist/semantic-bridge/compose.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type AnalysisQuestionPlan } from "./analysis-planner.js";
|
|
2
2
|
import { type AnalyticalPolicyContract, type MetricCapabilityContract } from "@duckcodeailabs/dql-core";
|
|
3
|
-
import type { LocalContextPack, MetadataObject } from "./catalog.js";
|
|
3
|
+
import type { LocalContextPack, MetadataObject, MetadataRetrievalLaneResult } from "./catalog.js";
|
|
4
4
|
import { type AgentRetrievalEvidence, type AgentRelationshipSafetyEvidence } from '../meaning-resolution.js';
|
|
5
5
|
import type { KnowledgeLens } from '../domain-context.js';
|
|
6
6
|
export type MetadataEvidenceClass = 'certified' | 'semantic' | 'sql';
|
|
@@ -20,6 +20,10 @@ export interface MetadataMeaningCandidate {
|
|
|
20
20
|
definition?: string;
|
|
21
21
|
formula?: string;
|
|
22
22
|
semanticModel?: string;
|
|
23
|
+
/** Immutable semantic compiler field identity, not a provider-facing alias. */
|
|
24
|
+
semanticRuntimeName?: string;
|
|
25
|
+
/** Source-authored semantic/member or physical column type. */
|
|
26
|
+
dataType?: string;
|
|
23
27
|
relevanceReasons: string[];
|
|
24
28
|
compatibilityFacts: string[];
|
|
25
29
|
businessShape: {
|
|
@@ -38,6 +42,11 @@ export interface MetadataMeaningCandidate {
|
|
|
38
42
|
/** Structured relationship facts when this card itself is a relationship. */
|
|
39
43
|
relationshipSafety?: AgentRelationshipSafetyEvidence[];
|
|
40
44
|
provenance?: string;
|
|
45
|
+
/** Actual snapshot retrieval memberships; never inferred from a display name. */
|
|
46
|
+
retrievalLanes?: Array<{
|
|
47
|
+
lane: 'exact' | 'lexical' | 'vector' | 'graph' | 'conversation';
|
|
48
|
+
rank?: number;
|
|
49
|
+
}>;
|
|
41
50
|
ambiguityPeerIds: string[];
|
|
42
51
|
}
|
|
43
52
|
export interface MetadataMeaningEvidencePackage {
|
|
@@ -49,6 +58,15 @@ export interface MetadataMeaningEvidencePackage {
|
|
|
49
58
|
* role-balanced.
|
|
50
59
|
*/
|
|
51
60
|
qualifiedCandidates?: MetadataMeaningCandidate[];
|
|
61
|
+
/**
|
|
62
|
+
* The one broad, immutable retrieval snapshot retained by an Ask host before
|
|
63
|
+
* it makes its own 32-item workspace and 16-card planner package. This is
|
|
64
|
+
* deliberately larger than `qualifiedCandidates`: a relevance-only first
|
|
65
|
+
* cut must not erase a physical product/order relation before role-balanced
|
|
66
|
+
* admission has a chance to reserve it. It is not a provider package and
|
|
67
|
+
* callers must still make a bounded, auditable admission decision.
|
|
68
|
+
*/
|
|
69
|
+
snapshotCandidates?: MetadataMeaningCandidate[];
|
|
52
70
|
byEvidenceClass: Record<MetadataEvidenceClass, MetadataMeaningCandidate[]>;
|
|
53
71
|
ambiguousGroups: Array<{
|
|
54
72
|
candidateIds: string[];
|
|
@@ -65,6 +83,14 @@ export interface AgentRetrievalEvidenceAdapterOptions {
|
|
|
65
83
|
analyticalPolicies?: AnalyticalPolicyContract[];
|
|
66
84
|
/** Same immutable context-pack objects used to prove member/value bindings. */
|
|
67
85
|
contextObjects?: MetadataObject[];
|
|
86
|
+
/** Snapshot retrieval results used to preserve multi-lane provenance. */
|
|
87
|
+
retrievalLanes?: MetadataRetrievalLaneResult[];
|
|
88
|
+
/**
|
|
89
|
+
* Ask Analyst Runtime consumes the broad immutable snapshot and performs
|
|
90
|
+
* its own role-balanced 32/16 admission. Legacy callers retain the compact
|
|
91
|
+
* qualified package by default.
|
|
92
|
+
*/
|
|
93
|
+
preferSnapshotCandidates?: boolean;
|
|
68
94
|
}
|
|
69
95
|
export interface MeaningEvidenceInputCandidate {
|
|
70
96
|
row: MetadataObject;
|
|
@@ -78,7 +104,7 @@ export interface MeaningEvidenceInputCandidate {
|
|
|
78
104
|
* intentionally remain separate: certification labels an execution lane but
|
|
79
105
|
* never makes an unrelated candidate more relevant to the question.
|
|
80
106
|
*/
|
|
81
|
-
export declare function buildMeaningEvidencePackage(question: string, questionPlan: AnalysisQuestionPlan, ranked: MeaningEvidenceInputCandidate[]): MetadataMeaningEvidencePackage;
|
|
107
|
+
export declare function buildMeaningEvidencePackage(question: string, questionPlan: AnalysisQuestionPlan, ranked: MeaningEvidenceInputCandidate[], retrievalLanes?: MetadataRetrievalLaneResult[]): MetadataMeaningEvidencePackage;
|
|
82
108
|
export declare function toAgentRetrievalEvidence(evidence: MetadataMeaningEvidencePackage, questionPlan: AnalysisQuestionPlan, options?: AgentRetrievalEvidenceAdapterOptions): AgentRetrievalEvidence;
|
|
83
109
|
/**
|
|
84
110
|
* One host-neutral compatibility projection shared by CLI, Browser Ask, MCP,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meaning-evidence.d.ts","sourceRoot":"","sources":["../../src/metadata/meaning-evidence.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC9B,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"meaning-evidence.d.ts","sourceRoot":"","sources":["../../src/metadata/meaning-evidence.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { scoreMetadataObjectWithAnalysisPlan, } from "./analysis-planner.js";
|
|
2
2
|
import { normalizeMetricCapabilityContract, } from "@duckcodeailabs/dql-core";
|
|
3
|
-
import { candidateMatchesCategoricalDimensionRequirement } from '../analytical-orchestration.js';
|
|
3
|
+
import { candidateMatchesCategoricalDimensionRequirement, categoricalDimensionTermsMatch, } from '../analytical-orchestration.js';
|
|
4
4
|
import { certifiedCandidateExplicitlyCoversMeasures, } from '../meaning-resolution.js';
|
|
5
5
|
const GENERIC_MEANING_TOKENS = new Set([
|
|
6
6
|
'amount', 'value', 'metric', 'measure', 'total', 'overall', 'monthly', 'daily',
|
|
@@ -8,13 +8,15 @@ const GENERIC_MEANING_TOKENS = new Set([
|
|
|
8
8
|
]);
|
|
9
9
|
const MAX_CANDIDATES_PER_CLASS = 4;
|
|
10
10
|
const MAX_QUALIFIED_CANDIDATES = 32;
|
|
11
|
+
const MAX_SNAPSHOT_CANDIDATES = 80;
|
|
11
12
|
const ANALYTICAL_TIME_GRAINS = new Set(['day', 'week', 'month', 'quarter', 'year', 'season', 'period']);
|
|
12
13
|
/**
|
|
13
14
|
* Produce compact meaning-rich cards for an AI resolver. Relevance and trust
|
|
14
15
|
* intentionally remain separate: certification labels an execution lane but
|
|
15
16
|
* never makes an unrelated candidate more relevant to the question.
|
|
16
17
|
*/
|
|
17
|
-
export function buildMeaningEvidencePackage(question, questionPlan, ranked) {
|
|
18
|
+
export function buildMeaningEvidencePackage(question, questionPlan, ranked, retrievalLanes = []) {
|
|
19
|
+
const laneMembership = laneMembershipByObjectKey(retrievalLanes);
|
|
18
20
|
const questionText = normalizeText(question);
|
|
19
21
|
const questionTokens = tokenSet(questionText);
|
|
20
22
|
const eligible = ranked.flatMap((item) => {
|
|
@@ -43,14 +45,20 @@ export function buildMeaningEvidencePackage(question, questionPlan, ranked) {
|
|
|
43
45
|
eligible.sort((left, right) => right.relevanceScore - left.relevanceScore
|
|
44
46
|
|| left.item.rank - right.item.rank
|
|
45
47
|
|| left.item.row.objectKey.localeCompare(right.item.row.objectKey));
|
|
46
|
-
const
|
|
48
|
+
const snapshotEligible = eligible.slice(0, MAX_SNAPSHOT_CANDIDATES);
|
|
49
|
+
const qualifiedPeerMap = buildAmbiguityPeers(snapshotEligible.map((candidate) => ({
|
|
47
50
|
objectKey: candidate.item.row.objectKey,
|
|
48
51
|
aliases: candidate.aliases,
|
|
49
52
|
})));
|
|
53
|
+
const snapshotCounts = { certified: 0, semantic: 0, sql: 0 };
|
|
54
|
+
const snapshotCandidates = snapshotEligible.map((candidate) => {
|
|
55
|
+
snapshotCounts[candidate.evidenceClass] += 1;
|
|
56
|
+
return candidateCard(candidate.item.row, candidate.evidenceClass, snapshotCounts[candidate.evidenceClass], candidate.relevanceScore, candidate.aliases, candidate.relevanceReasons, qualifiedPeerMap.get(candidate.item.row.objectKey) ?? [], laneMembership.get(candidate.item.row.objectKey));
|
|
57
|
+
});
|
|
50
58
|
const qualifiedCounts = { certified: 0, semantic: 0, sql: 0 };
|
|
51
|
-
const qualifiedCandidates =
|
|
59
|
+
const qualifiedCandidates = snapshotEligible.slice(0, MAX_QUALIFIED_CANDIDATES).map((candidate) => {
|
|
52
60
|
qualifiedCounts[candidate.evidenceClass] += 1;
|
|
53
|
-
return candidateCard(candidate.item.row, candidate.evidenceClass, qualifiedCounts[candidate.evidenceClass], candidate.relevanceScore, candidate.aliases, candidate.relevanceReasons, qualifiedPeerMap.get(candidate.item.row.objectKey) ?? []);
|
|
61
|
+
return candidateCard(candidate.item.row, candidate.evidenceClass, qualifiedCounts[candidate.evidenceClass], candidate.relevanceScore, candidate.aliases, candidate.relevanceReasons, qualifiedPeerMap.get(candidate.item.row.objectKey) ?? [], laneMembership.get(candidate.item.row.objectKey));
|
|
54
62
|
});
|
|
55
63
|
const requiredSemanticMetricKey = questionPlan.requestedShape.measures.length > 0
|
|
56
64
|
? eligible.find((candidate) => candidate.evidenceClass === 'semantic'
|
|
@@ -103,12 +111,13 @@ export function buildMeaningEvidencePackage(question, questionPlan, ranked) {
|
|
|
103
111
|
};
|
|
104
112
|
for (const candidate of selected) {
|
|
105
113
|
const lane = byEvidenceClass[candidate.evidenceClass];
|
|
106
|
-
lane.push(candidateCard(candidate.item.row, candidate.evidenceClass, lane.length + 1, candidate.relevanceScore, candidate.aliases, candidate.relevanceReasons, peerMap.get(candidate.item.row.objectKey) ?? []));
|
|
114
|
+
lane.push(candidateCard(candidate.item.row, candidate.evidenceClass, lane.length + 1, candidate.relevanceScore, candidate.aliases, candidate.relevanceReasons, peerMap.get(candidate.item.row.objectKey) ?? [], laneMembership.get(candidate.item.row.objectKey)));
|
|
107
115
|
}
|
|
108
116
|
return {
|
|
109
117
|
candidates: ['certified', 'semantic', 'sql']
|
|
110
118
|
.flatMap((evidenceClass) => byEvidenceClass[evidenceClass]),
|
|
111
119
|
qualifiedCandidates,
|
|
120
|
+
snapshotCandidates,
|
|
112
121
|
byEvidenceClass,
|
|
113
122
|
ambiguousGroups: ambiguityGroups(peerMap),
|
|
114
123
|
};
|
|
@@ -371,11 +380,18 @@ function attachRelationshipEvidence(candidates, contextObjects) {
|
|
|
371
380
|
}
|
|
372
381
|
}
|
|
373
382
|
export function toAgentRetrievalEvidence(evidence, questionPlan, options = {}) {
|
|
374
|
-
const sourceCandidates = evidence.
|
|
375
|
-
? evidence.
|
|
376
|
-
: evidence.
|
|
383
|
+
const sourceCandidates = options.preferSnapshotCandidates && evidence.snapshotCandidates?.length
|
|
384
|
+
? evidence.snapshotCandidates
|
|
385
|
+
: evidence.qualifiedCandidates?.length
|
|
386
|
+
? evidence.qualifiedCandidates
|
|
387
|
+
: evidence.candidates;
|
|
377
388
|
const maxRelevance = Math.max(1, ...sourceCandidates.map((candidate) => candidate.relevanceScore));
|
|
378
|
-
const
|
|
389
|
+
const optionLaneMembership = laneMembershipByObjectKey(options.retrievalLanes ?? []);
|
|
390
|
+
const candidates = sourceCandidates.map((candidate) => agentCandidateFromMeaning(candidate, maxRelevance, candidate.retrievalLanes ?? optionLaneMembership.get(candidate.objectKey)));
|
|
391
|
+
// Keep runtime-value proof attached to the exact qualified physical column
|
|
392
|
+
// that owns it. The provider never receives these values; the Ask compiler
|
|
393
|
+
// can use them only after a planner has selected that categorical field.
|
|
394
|
+
attachSafeValueEvidence(candidates, options.contextObjects ?? []);
|
|
379
395
|
const trustedBindings = trustedMemberBindingEvidence(questionPlan, candidates, options.contextObjects ?? []);
|
|
380
396
|
for (const member of trustedBindings.candidates) {
|
|
381
397
|
if (!candidates.some((candidate) => candidate.id === member.id))
|
|
@@ -435,7 +451,7 @@ export function toAgentRetrievalEvidence(evidence, questionPlan, options = {}) {
|
|
|
435
451
|
},
|
|
436
452
|
};
|
|
437
453
|
}
|
|
438
|
-
function agentCandidateFromMeaning(candidate, maxRelevance = 1) {
|
|
454
|
+
function agentCandidateFromMeaning(candidate, maxRelevance = 1, retrievalLanes) {
|
|
439
455
|
return {
|
|
440
456
|
id: candidate.objectKey,
|
|
441
457
|
qualifiedId: candidate.qualifiedId,
|
|
@@ -452,6 +468,8 @@ function agentCandidateFromMeaning(candidate, maxRelevance = 1) {
|
|
|
452
468
|
provenance: candidate.provenance,
|
|
453
469
|
domain: candidate.domain,
|
|
454
470
|
semanticModel: candidate.semanticModel,
|
|
471
|
+
...(candidate.semanticRuntimeName ? { semanticRuntimeName: candidate.semanticRuntimeName } : {}),
|
|
472
|
+
dataType: candidate.dataType,
|
|
455
473
|
primaryEntity: candidate.businessShape.entities[0],
|
|
456
474
|
dimensions: dimensionLookupAliases(candidate.businessShape.dimensions),
|
|
457
475
|
timeGrains: candidate.businessShape.timeGrains,
|
|
@@ -467,8 +485,73 @@ function agentCandidateFromMeaning(candidate, maxRelevance = 1) {
|
|
|
467
485
|
analyticalCapability: candidate.analyticalCapability,
|
|
468
486
|
eligible: true,
|
|
469
487
|
exactMatch: candidate.relevanceReasons.includes("exact name or alias"),
|
|
488
|
+
...(retrievalLanes?.length ? { retrievalLanes } : {}),
|
|
470
489
|
};
|
|
471
490
|
}
|
|
491
|
+
/**
|
|
492
|
+
* Attach a bounded, snapshot-local runtime-value observation to its qualified
|
|
493
|
+
* physical column. This is stricter than lexical matching: both the physical
|
|
494
|
+
* relation and the terminal column must agree. It lets a later deterministic
|
|
495
|
+
* compiler bind `in Philadelphia` to one selected SQL column without making
|
|
496
|
+
* the literal or a source-table guess visible to the planner.
|
|
497
|
+
*/
|
|
498
|
+
function attachSafeValueEvidence(candidates, objects) {
|
|
499
|
+
const values = objects.flatMap((object) => {
|
|
500
|
+
if (object.objectType !== 'runtime_value')
|
|
501
|
+
return [];
|
|
502
|
+
const relation = firstString(object.payload?.relation);
|
|
503
|
+
const column = firstString(object.payload?.column);
|
|
504
|
+
const value = firstString(object.payload?.value);
|
|
505
|
+
const normalizedValue = firstString(object.payload?.normalizedValue)
|
|
506
|
+
?? (value ? normalizeText(value) : undefined);
|
|
507
|
+
if (!relation || !column || !value || !normalizedValue)
|
|
508
|
+
return [];
|
|
509
|
+
return [{ version: 1, relation, column, value, normalizedValue }];
|
|
510
|
+
});
|
|
511
|
+
if (values.length === 0)
|
|
512
|
+
return;
|
|
513
|
+
for (const candidate of candidates) {
|
|
514
|
+
if (candidate.kind !== 'sql_column')
|
|
515
|
+
continue;
|
|
516
|
+
const matching = values.filter((value) => physicalCandidateColumnMatches(candidate, value.column)
|
|
517
|
+
&& physicalCandidateRelationMatches(candidate, value.relation));
|
|
518
|
+
if (matching.length === 0)
|
|
519
|
+
continue;
|
|
520
|
+
const deduped = new Map();
|
|
521
|
+
for (const value of matching) {
|
|
522
|
+
const key = [
|
|
523
|
+
canonicalPhysicalRelation(value.relation),
|
|
524
|
+
normalizeIdentifier(value.column),
|
|
525
|
+
normalizeText(value.normalizedValue),
|
|
526
|
+
].join('|');
|
|
527
|
+
if (!deduped.has(key))
|
|
528
|
+
deduped.set(key, value);
|
|
529
|
+
}
|
|
530
|
+
candidate.safeValueEvidence = [...deduped.values()]
|
|
531
|
+
.sort((left, right) => left.relation.localeCompare(right.relation)
|
|
532
|
+
|| left.column.localeCompare(right.column)
|
|
533
|
+
|| left.normalizedValue.localeCompare(right.normalizedValue));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function physicalCandidateColumnMatches(candidate, column) {
|
|
537
|
+
const expected = normalizeIdentifier(column);
|
|
538
|
+
return [candidate.name, ...(candidate.aliases ?? [])]
|
|
539
|
+
.some((value) => normalizeIdentifier(value) === expected);
|
|
540
|
+
}
|
|
541
|
+
function physicalCandidateRelationMatches(candidate, relation) {
|
|
542
|
+
const expected = canonicalPhysicalRelation(relation);
|
|
543
|
+
// A qualified physical source relation is mandatory. Do not infer one from
|
|
544
|
+
// a display name or a loose candidate ID, because that would allow a value
|
|
545
|
+
// observation from another table to authorize a filter.
|
|
546
|
+
return (candidate.sourceObjects ?? []).some((source) => {
|
|
547
|
+
const actual = canonicalPhysicalRelation(source);
|
|
548
|
+
return actual === expected || actual.endsWith(`.${expected}`) || expected.endsWith(`.${actual}`);
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
function canonicalPhysicalRelation(value) {
|
|
552
|
+
return normalizeRelation(value)
|
|
553
|
+
.replace(/^(?:runtime:relation:|dbt:model:|dbt:source:|sql:table:)/, '');
|
|
554
|
+
}
|
|
472
555
|
function trustedMemberBindingEvidence(plan, candidates, objects) {
|
|
473
556
|
const dimensions = new Map();
|
|
474
557
|
for (const object of objects) {
|
|
@@ -581,6 +664,87 @@ function supplementalClarificationEvidence(plan, objects, existing) {
|
|
|
581
664
|
exactMatch: false,
|
|
582
665
|
}];
|
|
583
666
|
}));
|
|
667
|
+
// A compact retrieval package can contain the selected semantic metric but
|
|
668
|
+
// not a separately indexed card for one of that metric's declared grouping
|
|
669
|
+
// dimensions. Retain one exact, same-snapshot extension for that field when
|
|
670
|
+
// the request names its categorical role. This does not infer a join or a
|
|
671
|
+
// business alias: the metric capability owns both the qualified dimension
|
|
672
|
+
// id and the MetricFlow-native grouping path, and the router later admits it
|
|
673
|
+
// only beside this exact metric. It is intentionally separate from the
|
|
674
|
+
// geography-only recovery below, which may bind a location-like field for
|
|
675
|
+
// the distinct business term `region` under a stricter sole-field rule.
|
|
676
|
+
const exactCapabilityDimensionCards = existing.flatMap((metric) => {
|
|
677
|
+
if (metric.kind !== 'semantic_metric')
|
|
678
|
+
return [];
|
|
679
|
+
const capability = normalizeMetricCapabilityContract(metric.analyticalCapability);
|
|
680
|
+
if (!capability || !capability.executionCapabilities.some((execution) => execution.route === 'semantic'))
|
|
681
|
+
return [];
|
|
682
|
+
const metricId = metric.qualifiedId ?? metric.id;
|
|
683
|
+
return requestedDimensions.flatMap((requestedTerm) => capability.dimensions.flatMap((dimension) => {
|
|
684
|
+
if (!dimension.supportedRoles.includes('group_by'))
|
|
685
|
+
return [];
|
|
686
|
+
// `existing` is the unbounded retrieval snapshot, not the compact
|
|
687
|
+
// meaning package. A separately indexed semantic dimension can be
|
|
688
|
+
// present here yet later lose its role-balanced admission to a raw/dbt
|
|
689
|
+
// column with the same business words. Preserve the capability-backed
|
|
690
|
+
// extension in that case so the router can admit it beside its exact
|
|
691
|
+
// metric. The final de-duplication deliberately retains this extension
|
|
692
|
+
// (with its metric/role proof) over a plain dimension card.
|
|
693
|
+
const identities = [
|
|
694
|
+
dimension.dimensionId,
|
|
695
|
+
dimension.label ?? '',
|
|
696
|
+
...(dimension.aliases ?? []),
|
|
697
|
+
];
|
|
698
|
+
if (!identities.some((identity) => categoricalDimensionTermsMatch(requestedTerm, identity)))
|
|
699
|
+
return [];
|
|
700
|
+
return [{
|
|
701
|
+
id: dimension.dimensionId,
|
|
702
|
+
qualifiedId: dimension.dimensionId,
|
|
703
|
+
kind: 'semantic_member',
|
|
704
|
+
semanticObjectType: 'dimension',
|
|
705
|
+
trustTier: 'semantic',
|
|
706
|
+
name: dimension.label ?? dimension.dimensionId.split(/[.:/]/).at(-1) ?? dimension.dimensionId,
|
|
707
|
+
aliases: uniqueStrings([
|
|
708
|
+
dimension.label ?? '',
|
|
709
|
+
...(dimension.aliases ?? []),
|
|
710
|
+
requestedTerm,
|
|
711
|
+
]),
|
|
712
|
+
definition: `Same-snapshot MetricFlow grouping field for ${requestedTerm}.`,
|
|
713
|
+
primaryEntity: dimension.entityId,
|
|
714
|
+
dimensions: [dimension.dimensionId],
|
|
715
|
+
sourceObjects: metric.sourceObjects,
|
|
716
|
+
relationshipEvidence: metric.relationshipEvidence,
|
|
717
|
+
relationshipSafety: metric.relationshipSafety,
|
|
718
|
+
relevanceScore: metric.relevanceScore,
|
|
719
|
+
matchReasons: ['same snapshot exact MetricFlow grouping extension'],
|
|
720
|
+
compatibility: 'compatible',
|
|
721
|
+
eligible: true,
|
|
722
|
+
exactMatch: false,
|
|
723
|
+
sameSnapshotRoleExtension: {
|
|
724
|
+
version: 1,
|
|
725
|
+
role: 'categorical_dimension',
|
|
726
|
+
requestedTerm,
|
|
727
|
+
metricId,
|
|
728
|
+
dimensionId: dimension.dimensionId,
|
|
729
|
+
basis: 'exact_metricflow_grouping_dimension',
|
|
730
|
+
},
|
|
731
|
+
}];
|
|
732
|
+
}));
|
|
733
|
+
});
|
|
734
|
+
// A location-like semantic dimension is not generally a synonym for
|
|
735
|
+
// "region". There is one deliberately narrow exception: when every
|
|
736
|
+
// current-turn requested metric proves the same one MetricFlow grouping
|
|
737
|
+
// dimension in this immutable snapshot. Surface that card as a typed,
|
|
738
|
+
// host-authored extension so the bounded meaning call can bind it; the
|
|
739
|
+
// selected metric capability still verifies the native grouping path and
|
|
740
|
+
// remains the only execution authority. This recovers the real Jaffle
|
|
741
|
+
// `revenue by ... region` path without guessing a raw join or widening
|
|
742
|
+
// lexical geography matching.
|
|
743
|
+
const sameSnapshotRoleExtensions = sameSnapshotGeographyRoleExtensions({
|
|
744
|
+
requestedMeasures,
|
|
745
|
+
requestedDimensions,
|
|
746
|
+
existing,
|
|
747
|
+
});
|
|
584
748
|
// A bare ranking ("who are the top customers") names an ENTITY and no
|
|
585
749
|
// measure, so every lane above — each keyed on a REQUESTED measure or
|
|
586
750
|
// dimension term — returns nothing, and the clarification has no choices to
|
|
@@ -624,8 +788,32 @@ function supplementalClarificationEvidence(plan, objects, existing) {
|
|
|
624
788
|
return [card];
|
|
625
789
|
}).sort((left, right) => right.relevanceScore - left.relevanceScore || left.id.localeCompare(right.id));
|
|
626
790
|
})();
|
|
627
|
-
|
|
628
|
-
|
|
791
|
+
// The index may yield a generic semantic-dimension card and then this
|
|
792
|
+
// function may enrich that exact qualified ID with a metric-declared,
|
|
793
|
+
// same-snapshot role proof. Retain the enriched card: it is not a second
|
|
794
|
+
// candidate or a guessed relationship, but the only metadata authority that
|
|
795
|
+
// says this field is queryable with the selected MetricFlow metric for the
|
|
796
|
+
// current requested role. First-seen de-duplication discarded that proof in
|
|
797
|
+
// real local indexes and manufactured a false coverage gap for
|
|
798
|
+
// `product category` / `product_type`.
|
|
799
|
+
const uniqueByQualifiedId = new Map();
|
|
800
|
+
for (const candidate of [
|
|
801
|
+
...objectCards,
|
|
802
|
+
...exactCapabilityDimensionCards,
|
|
803
|
+
...declaredDimensionCards,
|
|
804
|
+
...sameSnapshotRoleExtensions,
|
|
805
|
+
...rankingMeasureCards,
|
|
806
|
+
]) {
|
|
807
|
+
const key = candidate.qualifiedId ?? candidate.id;
|
|
808
|
+
const existingCandidate = uniqueByQualifiedId.get(key);
|
|
809
|
+
if (!existingCandidate
|
|
810
|
+
|| (!existingCandidate.sameSnapshotRoleExtension && candidate.sameSnapshotRoleExtension)) {
|
|
811
|
+
// Map replacement preserves stable first-seen ordering while retaining
|
|
812
|
+
// the stricter extension provenance for this one qualified object.
|
|
813
|
+
uniqueByQualifiedId.set(key, candidate);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
const unique = [...uniqueByQualifiedId.values()];
|
|
629
817
|
const roleLanes = [
|
|
630
818
|
...requestedDimensions.map((term) => ({ kind: 'dimension', term })),
|
|
631
819
|
...requestedMeasures.map((term) => ({ kind: 'metric', term })),
|
|
@@ -658,6 +846,95 @@ function supplementalClarificationEvidence(plan, objects, existing) {
|
|
|
658
846
|
}
|
|
659
847
|
return selected.slice(0, MAX_SUPPLEMENTAL_CANDIDATES);
|
|
660
848
|
}
|
|
849
|
+
function sameSnapshotGeographyRoleExtensions(input) {
|
|
850
|
+
const geographyTerms = [...new Set(input.requestedDimensions
|
|
851
|
+
.map(normalizeText)
|
|
852
|
+
.filter((term) => term === 'region' || term === 'geography' || term === 'geographic'))];
|
|
853
|
+
if (geographyTerms.length === 0 || input.requestedMeasures.length === 0)
|
|
854
|
+
return [];
|
|
855
|
+
const existingGeography = input.existing.some((candidate) => geographyTerms.some((term) => candidateMatchesCategoricalDimensionRequirement(candidate, [term])));
|
|
856
|
+
if (existingGeography)
|
|
857
|
+
return [];
|
|
858
|
+
const canonicalMetricTerm = (value) => {
|
|
859
|
+
const normalized = normalizeText(value);
|
|
860
|
+
return normalized === 'sale' || normalized === 'sales' ? 'revenue' : normalized;
|
|
861
|
+
};
|
|
862
|
+
const metricMatches = (candidate, requested) => {
|
|
863
|
+
if (candidate.kind !== 'semantic_metric' || !candidate.analyticalCapability)
|
|
864
|
+
return false;
|
|
865
|
+
const expected = canonicalMetricTerm(requested);
|
|
866
|
+
return [candidate.name, ...(candidate.aliases ?? []), candidate.analyticalCapability.metricId]
|
|
867
|
+
.map(canonicalMetricTerm)
|
|
868
|
+
.some((identity) => phraseTermsMatch(expected, identity));
|
|
869
|
+
};
|
|
870
|
+
const geographicDimension = (dimension) => {
|
|
871
|
+
const identity = [dimension.dimensionId, dimension.entityId, dimension.label ?? '', ...(dimension.aliases ?? [])]
|
|
872
|
+
.map(normalizeText)
|
|
873
|
+
.join(' ');
|
|
874
|
+
return /\b(?:region|geograph(?:y|ic)|location|country|state|province|city|territory)\b/.test(identity);
|
|
875
|
+
};
|
|
876
|
+
const candidatesForMeasure = (requested) => input.existing.flatMap((candidate) => {
|
|
877
|
+
if (!metricMatches(candidate, requested))
|
|
878
|
+
return [];
|
|
879
|
+
const capability = candidate.analyticalCapability;
|
|
880
|
+
if (!capability.executionCapabilities.some((execution) => execution.route === 'semantic'))
|
|
881
|
+
return [];
|
|
882
|
+
return capability.dimensions
|
|
883
|
+
.filter((dimension) => dimension.supportedRoles.includes('group_by') && geographicDimension(dimension))
|
|
884
|
+
.map((dimension) => ({ candidate, dimension }));
|
|
885
|
+
});
|
|
886
|
+
const matchesByMeasure = input.requestedMeasures.map((requested) => ({
|
|
887
|
+
requested,
|
|
888
|
+
matches: candidatesForMeasure(requested),
|
|
889
|
+
}));
|
|
890
|
+
if (matchesByMeasure.some(({ matches }) => matches.length === 0))
|
|
891
|
+
return [];
|
|
892
|
+
const sharedDimensionIds = matchesByMeasure
|
|
893
|
+
.map(({ matches }) => new Set(matches.map(({ dimension }) => dimension.dimensionId)))
|
|
894
|
+
.reduce((shared, ids) => shared === undefined
|
|
895
|
+
? ids
|
|
896
|
+
: new Set([...shared].filter((id) => ids.has(id))), undefined);
|
|
897
|
+
if (!sharedDimensionIds || sharedDimensionIds.size !== 1)
|
|
898
|
+
return [];
|
|
899
|
+
const dimensionId = [...sharedDimensionIds][0];
|
|
900
|
+
const witness = matchesByMeasure[0].matches.find((match) => match.dimension.dimensionId === dimensionId);
|
|
901
|
+
if (!witness)
|
|
902
|
+
return [];
|
|
903
|
+
const { candidate: metricCandidate, dimension } = witness;
|
|
904
|
+
const requestedTerm = geographyTerms[0];
|
|
905
|
+
return [{
|
|
906
|
+
id: dimensionId,
|
|
907
|
+
qualifiedId: dimensionId,
|
|
908
|
+
kind: 'semantic_member',
|
|
909
|
+
semanticObjectType: 'dimension',
|
|
910
|
+
trustTier: 'semantic',
|
|
911
|
+
name: dimension.label ?? dimension.dimensionId.split(/[.:/]/).at(-1) ?? dimension.dimensionId,
|
|
912
|
+
aliases: uniqueStrings([
|
|
913
|
+
dimension.label ?? '',
|
|
914
|
+
...(dimension.aliases ?? []),
|
|
915
|
+
requestedTerm,
|
|
916
|
+
]),
|
|
917
|
+
definition: `Same-snapshot MetricFlow grouping field for ${requestedTerm}.`,
|
|
918
|
+
primaryEntity: dimension.entityId,
|
|
919
|
+
dimensions: [dimensionId],
|
|
920
|
+
sourceObjects: metricCandidate.sourceObjects,
|
|
921
|
+
relationshipEvidence: metricCandidate.relationshipEvidence,
|
|
922
|
+
relationshipSafety: metricCandidate.relationshipSafety,
|
|
923
|
+
relevanceScore: metricCandidate.relevanceScore,
|
|
924
|
+
matchReasons: ['same snapshot role-targeted MetricFlow grouping extension'],
|
|
925
|
+
compatibility: 'compatible',
|
|
926
|
+
eligible: true,
|
|
927
|
+
exactMatch: false,
|
|
928
|
+
sameSnapshotRoleExtension: {
|
|
929
|
+
version: 1,
|
|
930
|
+
role: 'categorical_dimension',
|
|
931
|
+
requestedTerm,
|
|
932
|
+
metricId: metricCandidate.qualifiedId ?? metricCandidate.id,
|
|
933
|
+
dimensionId,
|
|
934
|
+
basis: 'sole_metricflow_grouping_dimension',
|
|
935
|
+
},
|
|
936
|
+
}];
|
|
937
|
+
}
|
|
661
938
|
function expandedRequestedTerms(terms, objects) {
|
|
662
939
|
const vocabulary = new Map();
|
|
663
940
|
for (const object of objects) {
|
|
@@ -832,13 +1109,45 @@ export function applyContextPackCompatibility(evidence, pack, selectedEvidenceId
|
|
|
832
1109
|
// paraphrase "What is monthly revenue?". Do not promote lexical
|
|
833
1110
|
// relevance alone; require the catalog's executable fit verdict.
|
|
834
1111
|
const requestedMeasures = pack.questionPlan.requestedShape.measures;
|
|
835
|
-
|
|
1112
|
+
// `declaredOutputs` is preferable because it remains independently
|
|
1113
|
+
// visible in the block source. Some older-but-valid certified blocks
|
|
1114
|
+
// only expose their projection through the catalog's immutable SQL
|
|
1115
|
+
// contract, however. Preserve that proof *only* when the reader typed
|
|
1116
|
+
// the exact block title and the same snapshot's catalog has already
|
|
1117
|
+
// selected this precise block as a high-confidence certified answer.
|
|
1118
|
+
// A fuzzy alias, paraphrase, or ordinary semantic question cannot use
|
|
1119
|
+
// this bridge, so non-exact role and output constraints remain intact.
|
|
1120
|
+
const exactCertifiedTitle = typeof pack.questionPlan.question === 'string'
|
|
1121
|
+
&& normalizeText(pack.questionPlan.question) === normalizeText(candidate.name)
|
|
1122
|
+
&& pack.routeDecision.route === 'certified'
|
|
1123
|
+
&& pack.routeDecision.exactObjectKey === candidate.id
|
|
1124
|
+
&& fit?.action === 'certified_answer'
|
|
1125
|
+
&& (fit.fit.kind === 'exact' || fit.fit.kind === 'trim_safe')
|
|
1126
|
+
&& fit.fit.confidence === 'high';
|
|
1127
|
+
const compatibilityFacts = exactCertifiedTitle
|
|
1128
|
+
? uniqueStrings([
|
|
1129
|
+
...(candidate.compatibilityFacts ?? []),
|
|
1130
|
+
...requestedMeasures.map((measure) => `catalog-proven-output: ${measure}`),
|
|
1131
|
+
// The catalog fit also proves this block's declared result
|
|
1132
|
+
// grain. Preserve it as an output only on the exact-title
|
|
1133
|
+
// bridge, so the immutable plan can bind `customer` to its
|
|
1134
|
+
// actual `customer_name` result without making a fuzzy block
|
|
1135
|
+
// title a display-key authority.
|
|
1136
|
+
...(candidate.compatibilityFacts ?? []).flatMap((fact) => /^grain:\s*(.+)$/i.exec(fact)?.[1]
|
|
1137
|
+
?.split(',')
|
|
1138
|
+
.map((value) => value.trim())
|
|
1139
|
+
.filter(Boolean)
|
|
1140
|
+
.map((value) => `catalog-proven-output: ${value}`) ?? []),
|
|
1141
|
+
])
|
|
1142
|
+
: candidate.compatibilityFacts;
|
|
1143
|
+
const blockDeclaresRequestedMeasures = certifiedCandidateExplicitlyCoversMeasures({ ...candidate, compatibilityFacts }, requestedMeasures);
|
|
836
1144
|
const completeCertifiedFit = fit?.action === 'certified_answer'
|
|
837
1145
|
&& (fit.fit.kind === 'exact' || fit.fit.kind === 'trim_safe')
|
|
838
1146
|
&& fit.fit.confidence === 'high'
|
|
839
1147
|
&& blockDeclaresRequestedMeasures;
|
|
840
1148
|
return {
|
|
841
1149
|
...candidate,
|
|
1150
|
+
...(compatibilityFacts ? { compatibilityFacts } : {}),
|
|
842
1151
|
// Lexical equality to a block name/alias is useful retrieval signal,
|
|
843
1152
|
// but it is not exact analytical authority. The catalog's complete
|
|
844
1153
|
// certified fit is the one host-owned exception and is still checked
|
|
@@ -915,11 +1224,31 @@ function trustTierFor(object, evidenceClass) {
|
|
|
915
1224
|
return 'governed_sql';
|
|
916
1225
|
return 'exploratory';
|
|
917
1226
|
}
|
|
918
|
-
function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases, relevanceReasons, ambiguityPeerIds) {
|
|
1227
|
+
function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases, relevanceReasons, ambiguityPeerIds, retrievalLanes) {
|
|
919
1228
|
const payload = object.payload ?? {};
|
|
1229
|
+
const typeParams = recordValue(payload.typeParams) ?? recordValue(payload.type_params) ?? {};
|
|
1230
|
+
const dataType = firstString(payload.dataType, payload.data_type, payload.columnType, payload.column_type, payload.dimensionType, payload.dimension_type, payload.type, typeParams.dataType, typeParams.data_type);
|
|
1231
|
+
const declaredTimeGrain = firstString(payload.timeGranularity, payload.time_granularity, typeParams.timeGranularity, typeParams.time_granularity);
|
|
1232
|
+
const semanticRuntimeName = evidenceClass === 'semantic'
|
|
1233
|
+
? firstString(payload.localId, payload.local_id)
|
|
1234
|
+
: undefined;
|
|
920
1235
|
const analyticalCapability = normalizeMetricCapabilityContract(payload.analyticalCapability);
|
|
921
1236
|
const relationshipSafety = relationshipSafetyFromObject(object);
|
|
922
|
-
|
|
1237
|
+
// A dbt/runtime column inherits its parent node's `uniqueId`. That value
|
|
1238
|
+
// is useful lineage metadata, but it is not a column identity: using it as
|
|
1239
|
+
// the candidate's qualified ID collapsed every selected column on a model
|
|
1240
|
+
// into the model itself. The physical cascade then appeared safe but could
|
|
1241
|
+
// not freeze an exact output contract. Keep the parent relation in
|
|
1242
|
+
// `sourceRelations`; give a column its source-qualified field identity.
|
|
1243
|
+
const columnObject = object.objectType.endsWith('_column');
|
|
1244
|
+
// Semantic registry IDs are model-qualified execution identities. The
|
|
1245
|
+
// historic `qualifiedId` field on a MetricFlow time dimension was only
|
|
1246
|
+
// domain + local name (`semantic:uncategorized:dimension:metric_time`), so
|
|
1247
|
+
// every model's synthesized global axis collapsed into one V2 capability.
|
|
1248
|
+
// Prefer the registry-qualified identity captured by the KG for semantic
|
|
1249
|
+
// cards; retain the older value in aliases for bounded backward reads.
|
|
1250
|
+
// Physical columns intentionally preserve their own source-qualified rule.
|
|
1251
|
+
const qualifiedId = firstString(...(evidenceClass === 'semantic' ? [payload.registryQualifiedId] : []), payload.qualifiedId, ...(columnObject ? [object.fullName] : []), payload.uniqueId, object.fullName, object.objectKey) ?? object.objectKey;
|
|
923
1252
|
const parameters = arrayNames(payload.parameters ?? payload.parameterPolicy);
|
|
924
1253
|
const outputs = uniqueStrings([
|
|
925
1254
|
...stringArray(payload.declaredOutputs),
|
|
@@ -963,6 +1292,12 @@ function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases
|
|
|
963
1292
|
? truncate(firstString(payload.formula, payload.expr, payload.sql), 320)
|
|
964
1293
|
: undefined,
|
|
965
1294
|
semanticModel: firstString(payload.semanticModel, payload.cube),
|
|
1295
|
+
// dbt/MetricFlow semantic objects often use a model-qualified display
|
|
1296
|
+
// name (`order_item.metric_time`) while the compiler accepts their local
|
|
1297
|
+
// declaration (`metric_time`). Preserve the latter only as host-side
|
|
1298
|
+
// capability data. It must never become a fuzzy retrieval alias.
|
|
1299
|
+
...(semanticRuntimeName ? { semanticRuntimeName } : {}),
|
|
1300
|
+
...(dataType ? { dataType } : {}),
|
|
966
1301
|
relevanceReasons,
|
|
967
1302
|
compatibilityFacts,
|
|
968
1303
|
businessShape: {
|
|
@@ -973,6 +1308,14 @@ function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases
|
|
|
973
1308
|
timeGrains: uniqueStrings([
|
|
974
1309
|
...stringArray(payload.timeGrains),
|
|
975
1310
|
...stringArray(payload.supportedTimeGrains),
|
|
1311
|
+
// MetricFlow/dbt semantic dimensions are indexed with their authored
|
|
1312
|
+
// `granularities` array. Keep that declaration in the bounded
|
|
1313
|
+
// retrieval card: Ask V2 validates a selected time grain against this
|
|
1314
|
+
// exact snapshot-local capability and must not infer one from a name.
|
|
1315
|
+
...stringArray(payload.granularities),
|
|
1316
|
+
...stringArray(payload.queryableGranularities),
|
|
1317
|
+
...stringArray(payload.queryable_granularities),
|
|
1318
|
+
declaredTimeGrain ?? '',
|
|
976
1319
|
]).slice(0, 8),
|
|
977
1320
|
parameters: parameters.slice(0, 12),
|
|
978
1321
|
filters: stringArray(payload.allowedFilters).slice(0, 12),
|
|
@@ -986,9 +1329,23 @@ function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases
|
|
|
986
1329
|
...(analyticalCapability ? { analyticalCapability } : {}),
|
|
987
1330
|
...(relationshipSafety ? { relationshipSafety: [relationshipSafety] } : {}),
|
|
988
1331
|
provenance: firstString(payload.provenance, object.sourceSystem),
|
|
1332
|
+
...(retrievalLanes?.length ? { retrievalLanes } : {}),
|
|
989
1333
|
ambiguityPeerIds,
|
|
990
1334
|
};
|
|
991
1335
|
}
|
|
1336
|
+
function laneMembershipByObjectKey(lanes) {
|
|
1337
|
+
const memberships = new Map();
|
|
1338
|
+
for (const lane of lanes) {
|
|
1339
|
+
for (const candidate of lane.candidates) {
|
|
1340
|
+
const next = memberships.get(candidate.objectKey) ?? [];
|
|
1341
|
+
if (!next.some((entry) => entry.lane === lane.lane && entry.rank === candidate.rank)) {
|
|
1342
|
+
next.push({ lane: lane.lane, rank: candidate.rank });
|
|
1343
|
+
}
|
|
1344
|
+
memberships.set(candidate.objectKey, next);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return memberships;
|
|
1348
|
+
}
|
|
992
1349
|
function agentEvidenceKind(candidate) {
|
|
993
1350
|
if (candidate.evidenceClass === 'certified')
|
|
994
1351
|
return 'certified_block';
|