@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type AnalysisQuestionPlan } from "./analysis-planner.js";
|
|
2
2
|
import { type AnalyticalPolicyContract, type MetricCapabilityContract } from "@duckcodeailabs/dql-core";
|
|
3
3
|
import type { LocalContextPack, MetadataObject } from "./catalog.js";
|
|
4
|
-
import type
|
|
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';
|
|
7
7
|
export type MetadataEvidenceTrust = 'certified' | 'semantic' | 'governed_sql' | 'exploratory';
|
|
@@ -35,11 +35,20 @@ export interface MetadataMeaningCandidate {
|
|
|
35
35
|
};
|
|
36
36
|
/** Snapshot-normalized execution truth; absent means capability is incomplete. */
|
|
37
37
|
analyticalCapability?: MetricCapabilityContract;
|
|
38
|
+
/** Structured relationship facts when this card itself is a relationship. */
|
|
39
|
+
relationshipSafety?: AgentRelationshipSafetyEvidence[];
|
|
38
40
|
provenance?: string;
|
|
39
41
|
ambiguityPeerIds: string[];
|
|
40
42
|
}
|
|
41
43
|
export interface MetadataMeaningEvidencePackage {
|
|
42
44
|
candidates: MetadataMeaningCandidate[];
|
|
45
|
+
/**
|
|
46
|
+
* Snapshot-qualified retrieval retained for host-side role admission. The
|
|
47
|
+
* historic per-class cards remain for compact UI diagnostics, but must not
|
|
48
|
+
* erase an explicit metric/entity before the one bounded meaning package is
|
|
49
|
+
* role-balanced.
|
|
50
|
+
*/
|
|
51
|
+
qualifiedCandidates?: MetadataMeaningCandidate[];
|
|
43
52
|
byEvidenceClass: Record<MetadataEvidenceClass, MetadataMeaningCandidate[]>;
|
|
44
53
|
ambiguousGroups: Array<{
|
|
45
54
|
candidateIds: string[];
|
|
@@ -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,10 +1,13 @@
|
|
|
1
1
|
import { scoreMetadataObjectWithAnalysisPlan, } from "./analysis-planner.js";
|
|
2
2
|
import { normalizeMetricCapabilityContract, } from "@duckcodeailabs/dql-core";
|
|
3
|
+
import { candidateMatchesCategoricalDimensionRequirement } from '../analytical-orchestration.js';
|
|
4
|
+
import { certifiedCandidateExplicitlyCoversMeasures, } from '../meaning-resolution.js';
|
|
3
5
|
const GENERIC_MEANING_TOKENS = new Set([
|
|
4
6
|
'amount', 'value', 'metric', 'measure', 'total', 'overall', 'monthly', 'daily',
|
|
5
7
|
'weekly', 'quarterly', 'yearly', 'annual', 'count', 'number', 'rate', 'ratio',
|
|
6
8
|
]);
|
|
7
9
|
const MAX_CANDIDATES_PER_CLASS = 4;
|
|
10
|
+
const MAX_QUALIFIED_CANDIDATES = 32;
|
|
8
11
|
const ANALYTICAL_TIME_GRAINS = new Set(['day', 'week', 'month', 'quarter', 'year', 'season', 'period']);
|
|
9
12
|
/**
|
|
10
13
|
* Produce compact meaning-rich cards for an AI resolver. Relevance and trust
|
|
@@ -40,6 +43,15 @@ export function buildMeaningEvidencePackage(question, questionPlan, ranked) {
|
|
|
40
43
|
eligible.sort((left, right) => right.relevanceScore - left.relevanceScore
|
|
41
44
|
|| left.item.rank - right.item.rank
|
|
42
45
|
|| left.item.row.objectKey.localeCompare(right.item.row.objectKey));
|
|
46
|
+
const qualifiedPeerMap = buildAmbiguityPeers(eligible.slice(0, MAX_QUALIFIED_CANDIDATES).map((candidate) => ({
|
|
47
|
+
objectKey: candidate.item.row.objectKey,
|
|
48
|
+
aliases: candidate.aliases,
|
|
49
|
+
})));
|
|
50
|
+
const qualifiedCounts = { certified: 0, semantic: 0, sql: 0 };
|
|
51
|
+
const qualifiedCandidates = eligible.slice(0, MAX_QUALIFIED_CANDIDATES).map((candidate) => {
|
|
52
|
+
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) ?? []);
|
|
54
|
+
});
|
|
43
55
|
const requiredSemanticMetricKey = questionPlan.requestedShape.measures.length > 0
|
|
44
56
|
? eligible.find((candidate) => candidate.evidenceClass === 'semantic'
|
|
45
57
|
&& candidate.item.row.objectType === 'semantic_metric')?.item.row.objectKey
|
|
@@ -96,6 +108,7 @@ export function buildMeaningEvidencePackage(question, questionPlan, ranked) {
|
|
|
96
108
|
return {
|
|
97
109
|
candidates: ['certified', 'semantic', 'sql']
|
|
98
110
|
.flatMap((evidenceClass) => byEvidenceClass[evidenceClass]),
|
|
111
|
+
qualifiedCandidates,
|
|
99
112
|
byEvidenceClass,
|
|
100
113
|
ambiguousGroups: ambiguityGroups(peerMap),
|
|
101
114
|
};
|
|
@@ -114,17 +127,141 @@ export function buildMeaningEvidencePackage(question, questionPlan, ranked) {
|
|
|
114
127
|
* gates joins on that list, so every multi-relation governed compile returned
|
|
115
128
|
* `RELATIONSHIP_PROOF_REQUIRED` and the path was unreachable in production.
|
|
116
129
|
*
|
|
117
|
-
* This only *offers*
|
|
118
|
-
* enforces certified, fresh,
|
|
119
|
-
* join
|
|
130
|
+
* This only *offers* identities and compact source facts.
|
|
131
|
+
* `compileGovernedRelationalPlan` still enforces certified, fresh,
|
|
132
|
+
* automatic-join-safe (`REL-002`) before it emits a join. The router also
|
|
133
|
+
* requires the structured safety record for exploratory composition, so a
|
|
134
|
+
* draft relationship with a neutral-looking identity cannot suppress a
|
|
135
|
+
* clarification.
|
|
136
|
+
*/
|
|
137
|
+
function relationshipSafetyFromObject(object) {
|
|
138
|
+
if (object.objectType !== 'relationship')
|
|
139
|
+
return undefined;
|
|
140
|
+
const payload = object.payload ?? {};
|
|
141
|
+
const id = firstString(payload.qualifiedId, payload.id, object.fullName, object.objectKey);
|
|
142
|
+
if (!id)
|
|
143
|
+
return undefined;
|
|
144
|
+
const validationPayload = recordValue(payload.validation);
|
|
145
|
+
const keys = Array.isArray(payload.keys)
|
|
146
|
+
? payload.keys.flatMap((value) => {
|
|
147
|
+
const key = recordValue(value);
|
|
148
|
+
const from = firstString(key?.from);
|
|
149
|
+
const to = firstString(key?.to);
|
|
150
|
+
return from && to ? [{ from, to }] : [];
|
|
151
|
+
})
|
|
152
|
+
: [];
|
|
153
|
+
const validation = validationPayload
|
|
154
|
+
? {
|
|
155
|
+
status: firstString(validationPayload.status),
|
|
156
|
+
checkedAt: firstString(validationPayload.checkedAt, validationPayload.checked_at),
|
|
157
|
+
queryFingerprint: firstString(validationPayload.queryFingerprint, validationPayload.query_fingerprint),
|
|
158
|
+
proofFingerprint: firstString(validationPayload.proofFingerprint, validationPayload.proof_fingerprint),
|
|
159
|
+
}
|
|
160
|
+
: undefined;
|
|
161
|
+
return {
|
|
162
|
+
id,
|
|
163
|
+
aliases: uniqueStrings([
|
|
164
|
+
firstString(payload.qualifiedId) ?? '',
|
|
165
|
+
firstString(payload.id) ?? '',
|
|
166
|
+
firstString(payload.localId) ?? '',
|
|
167
|
+
object.objectKey,
|
|
168
|
+
object.fullName ?? '',
|
|
169
|
+
object.name,
|
|
170
|
+
]).filter((identity) => identity !== id),
|
|
171
|
+
from: firstString(payload.from),
|
|
172
|
+
to: firstString(payload.to),
|
|
173
|
+
keys,
|
|
174
|
+
status: firstString(payload.status, object.status),
|
|
175
|
+
cardinality: firstString(payload.cardinality),
|
|
176
|
+
fanout: firstString(payload.fanout),
|
|
177
|
+
...(typeof payload.staleCertification === 'boolean'
|
|
178
|
+
? { staleCertification: payload.staleCertification }
|
|
179
|
+
: {}),
|
|
180
|
+
...(typeof payload.automaticJoinAllowed === 'boolean'
|
|
181
|
+
? { automaticJoinAllowed: payload.automaticJoinAllowed }
|
|
182
|
+
: {}),
|
|
183
|
+
certificationFingerprint: firstString(payload.certificationFingerprint),
|
|
184
|
+
evidenceExpiresAt: firstString(payload.evidenceExpiresAt, payload.evidence_expires_at),
|
|
185
|
+
...(validation ? { validation } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function normalizedRelationshipReference(value) {
|
|
189
|
+
return value.trim().toLowerCase();
|
|
190
|
+
}
|
|
191
|
+
function relationshipReferenceLeaf(value) {
|
|
192
|
+
const normalized = normalizedRelationshipReference(value).replace(/["`\[\]]/g, '');
|
|
193
|
+
return normalized.split(/::|:|\./).filter(Boolean).at(-1) ?? normalized;
|
|
194
|
+
}
|
|
195
|
+
function candidateRelationshipReferences(candidate) {
|
|
196
|
+
return uniqueStrings([
|
|
197
|
+
candidate.qualifiedId ?? '',
|
|
198
|
+
candidate.id,
|
|
199
|
+
candidate.primaryEntity ?? '',
|
|
200
|
+
candidate.analyticalCapability?.primaryEntityId ?? '',
|
|
201
|
+
...(candidate.sourceObjects ?? []),
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
function isRawRelationCandidate(candidate) {
|
|
205
|
+
return candidate.kind === 'dbt_model'
|
|
206
|
+
|| candidate.kind === 'dbt_source'
|
|
207
|
+
|| candidate.kind === 'sql_table';
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Bind relationship proof only through a canonical entity/relation identity.
|
|
211
|
+
* A leaf fallback is deliberately allowed only when both the endpoint and the
|
|
212
|
+
* raw relation leaf occur exactly once in the retrieval snapshot. This keeps
|
|
213
|
+
* two domains with an `orders` model from inheriting each other's proof.
|
|
120
214
|
*/
|
|
121
215
|
function attachRelationshipEvidence(candidates, contextObjects) {
|
|
122
216
|
const relationships = contextObjects.filter((object) => object.objectType === 'relationship');
|
|
123
217
|
if (relationships.length === 0)
|
|
124
218
|
return;
|
|
125
|
-
const
|
|
219
|
+
const endpointIdsByReference = new Map();
|
|
220
|
+
const endpointIdsByLeaf = new Map();
|
|
221
|
+
const identitiesByEndpoint = new Map();
|
|
222
|
+
const safetyByEndpoint = new Map();
|
|
223
|
+
const registerEndpointReference = (endpoint, reference) => {
|
|
224
|
+
if (!reference?.trim())
|
|
225
|
+
return;
|
|
226
|
+
const normalizedReference = normalizedRelationshipReference(reference);
|
|
227
|
+
const existing = endpointIdsByReference.get(normalizedReference) ?? new Set();
|
|
228
|
+
existing.add(endpoint);
|
|
229
|
+
endpointIdsByReference.set(normalizedReference, existing);
|
|
230
|
+
};
|
|
231
|
+
const registerEndpoint = (endpoint) => {
|
|
232
|
+
const normalizedEndpoint = normalizedRelationshipReference(endpoint);
|
|
233
|
+
const existing = endpointIdsByLeaf.get(relationshipReferenceLeaf(endpoint)) ?? new Set();
|
|
234
|
+
existing.add(endpoint);
|
|
235
|
+
endpointIdsByLeaf.set(relationshipReferenceLeaf(endpoint), existing);
|
|
236
|
+
registerEndpointReference(endpoint, endpoint);
|
|
237
|
+
// Keep the canonical normalized key explicit even when endpoint casing
|
|
238
|
+
// differs from the relationship payload or a physical dbt reference.
|
|
239
|
+
registerEndpointReference(endpoint, normalizedEndpoint);
|
|
240
|
+
};
|
|
241
|
+
// DQL entity metadata is the authoritative bridge from a canonical modeling
|
|
242
|
+
// endpoint to its dbt/runtime relation. Preserve that bridge rather than
|
|
243
|
+
// reducing it to a leaf name.
|
|
244
|
+
for (const object of contextObjects.filter((item) => item.objectType === 'dql_entity')) {
|
|
245
|
+
const payload = object.payload ?? {};
|
|
246
|
+
const endpoint = firstString(payload.qualifiedId, payload.id, object.fullName, object.objectKey);
|
|
247
|
+
if (!endpoint)
|
|
248
|
+
continue;
|
|
249
|
+
registerEndpoint(endpoint);
|
|
250
|
+
for (const reference of [
|
|
251
|
+
payload.qualifiedId,
|
|
252
|
+
payload.id,
|
|
253
|
+
payload.localId,
|
|
254
|
+
payload.dbtUniqueId,
|
|
255
|
+
payload.relation,
|
|
256
|
+
payload.table,
|
|
257
|
+
object.objectKey,
|
|
258
|
+
object.fullName,
|
|
259
|
+
])
|
|
260
|
+
registerEndpointReference(endpoint, firstString(reference));
|
|
261
|
+
}
|
|
126
262
|
for (const object of relationships) {
|
|
127
263
|
const payload = (object.payload ?? {});
|
|
264
|
+
const safety = relationshipSafetyFromObject(object);
|
|
128
265
|
const identities = [payload.qualifiedId, payload.id, payload.localId, object.objectKey, object.fullName, object.name]
|
|
129
266
|
.filter((value) => Boolean(value));
|
|
130
267
|
if (identities.length === 0)
|
|
@@ -132,58 +269,113 @@ function attachRelationshipEvidence(candidates, contextObjects) {
|
|
|
132
269
|
for (const endpoint of [payload.from, payload.to]) {
|
|
133
270
|
if (!endpoint)
|
|
134
271
|
continue;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
272
|
+
registerEndpoint(endpoint);
|
|
273
|
+
const normalizedEndpoint = normalizedRelationshipReference(endpoint);
|
|
274
|
+
const endpointIdentities = identitiesByEndpoint.get(normalizedEndpoint) ?? new Set();
|
|
275
|
+
for (const identity of identities)
|
|
276
|
+
endpointIdentities.add(identity);
|
|
277
|
+
identitiesByEndpoint.set(normalizedEndpoint, endpointIdentities);
|
|
278
|
+
if (safety) {
|
|
279
|
+
const safetyForEndpoint = safetyByEndpoint.get(normalizedEndpoint) ?? new Map();
|
|
280
|
+
safetyForEndpoint.set(safety.id, safety);
|
|
281
|
+
safetyByEndpoint.set(normalizedEndpoint, safetyForEndpoint);
|
|
140
282
|
}
|
|
141
283
|
}
|
|
142
284
|
}
|
|
143
|
-
if (
|
|
285
|
+
if (identitiesByEndpoint.size === 0)
|
|
144
286
|
return;
|
|
287
|
+
const rawRelationIdsByLeaf = new Map();
|
|
288
|
+
const registerRawRelation = (relationId, references) => {
|
|
289
|
+
for (const leaf of new Set(references.map(relationshipReferenceLeaf))) {
|
|
290
|
+
const ids = rawRelationIdsByLeaf.get(leaf) ?? new Set();
|
|
291
|
+
ids.add(relationId);
|
|
292
|
+
rawRelationIdsByLeaf.set(leaf, ids);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
for (const candidate of candidates.filter(isRawRelationCandidate)) {
|
|
296
|
+
registerRawRelation(normalizedRelationshipReference(candidate.qualifiedId ?? candidate.id), candidateRelationshipReferences(candidate));
|
|
297
|
+
}
|
|
298
|
+
// Candidate cards are bounded, but the immutable context snapshot can retain
|
|
299
|
+
// another raw relation with the same leaf outside that compact package. Count
|
|
300
|
+
// it too before permitting a leaf fallback.
|
|
301
|
+
for (const object of contextObjects.filter((item) => item.objectType === 'dbt_model'
|
|
302
|
+
|| item.objectType === 'dbt_source'
|
|
303
|
+
|| item.objectType === 'warehouse_table'
|
|
304
|
+
|| item.objectType === 'runtime_table')) {
|
|
305
|
+
const payload = object.payload ?? {};
|
|
306
|
+
const relationId = firstString(payload.qualifiedId, payload.uniqueId, object.fullName, object.objectKey) ?? object.objectKey;
|
|
307
|
+
registerRawRelation(normalizedRelationshipReference(relationId), uniqueStrings([
|
|
308
|
+
relationId,
|
|
309
|
+
firstString(payload.qualifiedId) ?? '',
|
|
310
|
+
firstString(payload.uniqueId) ?? '',
|
|
311
|
+
firstString(payload.relation) ?? '',
|
|
312
|
+
firstString(payload.table) ?? '',
|
|
313
|
+
object.name,
|
|
314
|
+
object.fullName ?? '',
|
|
315
|
+
]));
|
|
316
|
+
}
|
|
317
|
+
const endpointsForReferences = (references) => {
|
|
318
|
+
const direct = new Set();
|
|
319
|
+
for (const reference of references) {
|
|
320
|
+
for (const endpoint of endpointIdsByReference.get(normalizedRelationshipReference(reference)) ?? []) {
|
|
321
|
+
direct.add(endpoint);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (direct.size > 0)
|
|
325
|
+
return direct;
|
|
326
|
+
const fallback = new Set();
|
|
327
|
+
for (const leaf of new Set(references.map(relationshipReferenceLeaf))) {
|
|
328
|
+
const endpoints = endpointIdsByLeaf.get(leaf);
|
|
329
|
+
if (endpoints?.size !== 1 || rawRelationIdsByLeaf.get(leaf)?.size !== 1)
|
|
330
|
+
continue;
|
|
331
|
+
for (const endpoint of endpoints)
|
|
332
|
+
fallback.add(endpoint);
|
|
333
|
+
}
|
|
334
|
+
return fallback;
|
|
335
|
+
};
|
|
145
336
|
for (const candidate of candidates) {
|
|
146
337
|
for (const dimension of candidate.analyticalCapability?.dimensions ?? []) {
|
|
147
338
|
const pathRelationships = new Set(dimension.relationshipPathIds ?? []);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
339
|
+
const endpoints = endpointsForReferences([dimension.entityId, ...(dimension.nativeGroupingPath ?? [])]);
|
|
340
|
+
for (const endpoint of endpoints) {
|
|
341
|
+
for (const identity of identitiesByEndpoint.get(normalizedRelationshipReference(endpoint)) ?? []) {
|
|
342
|
+
pathRelationships.add(identity);
|
|
152
343
|
}
|
|
153
344
|
}
|
|
154
345
|
if (pathRelationships.size > 0)
|
|
155
346
|
dimension.relationshipPathIds = [...pathRelationships].sort();
|
|
156
347
|
}
|
|
348
|
+
const endpoints = endpointsForReferences(candidateRelationshipReferences(candidate));
|
|
157
349
|
const touched = new Set();
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
...capabilityPathEntities,
|
|
166
|
-
...(candidate.sourceObjects ?? []),
|
|
167
|
-
]) {
|
|
168
|
-
if (!reference)
|
|
169
|
-
continue;
|
|
170
|
-
for (const alias of entityAliasesForRelationshipMatch(reference)) {
|
|
171
|
-
for (const identity of identitiesByEntity.get(alias) ?? [])
|
|
172
|
-
touched.add(identity);
|
|
350
|
+
const touchedSafety = new Map((candidate.relationshipSafety ?? []).map((safety) => [safety.id, safety]));
|
|
351
|
+
for (const endpoint of endpoints) {
|
|
352
|
+
const normalizedEndpoint = normalizedRelationshipReference(endpoint);
|
|
353
|
+
for (const identity of identitiesByEndpoint.get(normalizedEndpoint) ?? [])
|
|
354
|
+
touched.add(identity);
|
|
355
|
+
for (const safety of safetyByEndpoint.get(normalizedEndpoint)?.values() ?? []) {
|
|
356
|
+
touchedSafety.set(safety.id, safety);
|
|
173
357
|
}
|
|
174
358
|
}
|
|
359
|
+
if (endpoints.size > 0) {
|
|
360
|
+
candidate.relationshipEndpointIds = [...new Set([
|
|
361
|
+
...(candidate.relationshipEndpointIds ?? []),
|
|
362
|
+
...endpoints,
|
|
363
|
+
])].sort();
|
|
364
|
+
}
|
|
175
365
|
if (touched.size > 0)
|
|
176
366
|
candidate.relationshipEvidence = [...touched].sort();
|
|
367
|
+
if (touchedSafety.size > 0) {
|
|
368
|
+
candidate.relationshipSafety = [...touchedSafety.values()]
|
|
369
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
370
|
+
}
|
|
177
371
|
}
|
|
178
372
|
}
|
|
179
|
-
/** Match `commerce::entity::order`, `order`, and `entity:order` as one entity. */
|
|
180
|
-
function entityAliasesForRelationshipMatch(reference) {
|
|
181
|
-
const normalized = reference.toLowerCase().replace(/^entity:/, '');
|
|
182
|
-
return [...new Set([normalized, normalized.split('::').at(-1) ?? normalized])];
|
|
183
|
-
}
|
|
184
373
|
export function toAgentRetrievalEvidence(evidence, questionPlan, options = {}) {
|
|
185
|
-
const
|
|
186
|
-
|
|
374
|
+
const sourceCandidates = evidence.qualifiedCandidates?.length
|
|
375
|
+
? evidence.qualifiedCandidates
|
|
376
|
+
: evidence.candidates;
|
|
377
|
+
const maxRelevance = Math.max(1, ...sourceCandidates.map((candidate) => candidate.relevanceScore));
|
|
378
|
+
const candidates = sourceCandidates.map((candidate) => agentCandidateFromMeaning(candidate, maxRelevance));
|
|
187
379
|
const trustedBindings = trustedMemberBindingEvidence(questionPlan, candidates, options.contextObjects ?? []);
|
|
188
380
|
for (const member of trustedBindings.candidates) {
|
|
189
381
|
if (!candidates.some((candidate) => candidate.id === member.id))
|
|
@@ -265,6 +457,7 @@ function agentCandidateFromMeaning(candidate, maxRelevance = 1) {
|
|
|
265
457
|
timeGrains: candidate.businessShape.timeGrains,
|
|
266
458
|
requiredParameters: candidate.businessShape.parameters,
|
|
267
459
|
sourceObjects: candidate.businessShape.sourceRelations,
|
|
460
|
+
relationshipSafety: candidate.relationshipSafety,
|
|
268
461
|
relevanceScore: Number((candidate.relevanceScore / maxRelevance).toFixed(6)),
|
|
269
462
|
matchReasons: candidate.relevanceReasons,
|
|
270
463
|
// Fit validation remains host-owned. Metadata only reports facts here and
|
|
@@ -353,21 +546,21 @@ function supplementalClarificationEvidence(plan, objects, existing) {
|
|
|
353
546
|
if (!['semantic_metric', 'semantic_measure', 'semantic_dimension'].includes(object.objectType))
|
|
354
547
|
return [];
|
|
355
548
|
const names = aliasesFor(object).map(normalizeText);
|
|
356
|
-
const
|
|
549
|
+
const candidate = agentCandidateFromMeaning(candidateCard(object, 'semantic', 1, 1, aliasesFor(object), ['requested clarification capability'], []));
|
|
357
550
|
const relevant = object.objectType === 'semantic_metric' || object.objectType === 'semantic_measure'
|
|
358
551
|
? requestedMeasures.some((term) => names.some((name) => phraseTermsMatch(term, name)))
|
|
359
552
|
: requestedDimensions.some((term) => names.some((name) => phraseTermsMatch(term, name))
|
|
360
|
-
|| (
|
|
553
|
+
|| candidateMatchesCategoricalDimensionRequirement(candidate, [term]));
|
|
361
554
|
if (!relevant)
|
|
362
555
|
return [];
|
|
363
|
-
|
|
364
|
-
return [agentCandidateFromMeaning(card)];
|
|
556
|
+
return [candidate];
|
|
365
557
|
});
|
|
366
558
|
const declaredDimensionCards = existing.flatMap((candidate) => (candidate.dimensions ?? []).flatMap((dimensionId) => {
|
|
367
559
|
if (!/[:./]/.test(dimensionId) || existingIds.has(dimensionId))
|
|
368
560
|
return [];
|
|
369
561
|
const role = normalizeDimensionRole(dimensionId);
|
|
370
|
-
const relevant = requestedDimensions.some((term) => phraseTermsMatch(term, role)
|
|
562
|
+
const relevant = requestedDimensions.some((term) => phraseTermsMatch(term, role)
|
|
563
|
+
|| candidateMatchesCategoricalDimensionRequirement(candidate, [term]));
|
|
371
564
|
if (!relevant)
|
|
372
565
|
return [];
|
|
373
566
|
return [{
|
|
@@ -380,6 +573,7 @@ function supplementalClarificationEvidence(plan, objects, existing) {
|
|
|
380
573
|
definition: `Modeled ${role} dimension available through ${candidate.name}.`,
|
|
381
574
|
primaryEntity: candidate.primaryEntity,
|
|
382
575
|
dimensions: [dimensionId],
|
|
576
|
+
compatibilityFacts: candidate.compatibilityFacts,
|
|
383
577
|
relevanceScore: candidate.relevanceScore,
|
|
384
578
|
matchReasons: ['requested clarification capability role'],
|
|
385
579
|
compatibility: 'partial',
|
|
@@ -454,7 +648,7 @@ function supplementalClarificationEvidence(plan, objects, existing) {
|
|
|
454
648
|
if (candidate.kind !== 'semantic_member')
|
|
455
649
|
return false;
|
|
456
650
|
return candidateTerms.some((term) => phraseTermsMatch(lane.term, term))
|
|
457
|
-
|| (
|
|
651
|
+
|| candidateMatchesCategoricalDimensionRequirement(candidate, [lane.term]);
|
|
458
652
|
}).sort((left, right) => right.relevanceScore - left.relevanceScore || left.id.localeCompare(right.id));
|
|
459
653
|
for (const candidate of matches.slice(0, MAX_PER_REQUESTED_ROLE)) {
|
|
460
654
|
if (!selected.some((item) => (item.qualifiedId ?? item.id) === (candidate.qualifiedId ?? candidate.id))) {
|
|
@@ -526,9 +720,6 @@ function dimensionLookupAliases(dimensions) {
|
|
|
526
720
|
return role && role !== leaf ? [dimension, role] : [dimension];
|
|
527
721
|
}));
|
|
528
722
|
}
|
|
529
|
-
function isGeographicRole(value) {
|
|
530
|
-
return /^(?:region|location|geography|territory|market|site|store)$/.test(normalizeDimensionRole(value));
|
|
531
|
-
}
|
|
532
723
|
/**
|
|
533
724
|
* Bind current-turn values only when an authorized runtime-value observation
|
|
534
725
|
* and a semantic dimension's exact physical table/column identity agree. Text
|
|
@@ -633,23 +824,34 @@ export function applyContextPackCompatibility(evidence, pack, selectedEvidenceId
|
|
|
633
824
|
if (candidate.kind === 'certified_block') {
|
|
634
825
|
const fit = certifiedFits.get(candidate.id);
|
|
635
826
|
const unrequestedStaticScope = fit?.fit.reasons.some((reason) => reason.startsWith('certified static scope is not requested:')) ?? false;
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
827
|
+
// `certified_answer` is already the catalog's termination verdict: it
|
|
828
|
+
// binds the exact artifact to a high-confidence complete shape fit.
|
|
829
|
+
// An authored example remains one way to reach it, but it is not the
|
|
830
|
+
// only safe way. Requiring literal example wording made a compatible
|
|
831
|
+
// `monthly_revenue` artifact lose to a semantic metric for the ordinary
|
|
832
|
+
// paraphrase "What is monthly revenue?". Do not promote lexical
|
|
833
|
+
// relevance alone; require the catalog's executable fit verdict.
|
|
834
|
+
const requestedMeasures = pack.questionPlan.requestedShape.measures;
|
|
835
|
+
const blockDeclaresRequestedMeasures = certifiedCandidateExplicitlyCoversMeasures(candidate, requestedMeasures);
|
|
836
|
+
const completeCertifiedFit = fit?.action === 'certified_answer'
|
|
837
|
+
&& (fit.fit.kind === 'exact' || fit.fit.kind === 'trim_safe')
|
|
838
|
+
&& fit.fit.confidence === 'high'
|
|
839
|
+
&& blockDeclaresRequestedMeasures;
|
|
639
840
|
return {
|
|
640
841
|
...candidate,
|
|
641
842
|
// Lexical equality to a block name/alias is useful retrieval signal,
|
|
642
|
-
// but it is not exact analytical authority.
|
|
643
|
-
//
|
|
644
|
-
|
|
645
|
-
|
|
843
|
+
// but it is not exact analytical authority. The catalog's complete
|
|
844
|
+
// certified fit is the one host-owned exception and is still checked
|
|
845
|
+
// again when the immutable plan freezes.
|
|
846
|
+
exactMatch: completeCertifiedFit,
|
|
847
|
+
...(completeCertifiedFit
|
|
646
848
|
? { analyticalFitClass: 'exact' }
|
|
647
849
|
: { analyticalFitClass: undefined }),
|
|
648
850
|
// A statically scoped certified artifact is not an alternative for a
|
|
649
851
|
// broader question. Exclude it before both meaning resolution and
|
|
650
852
|
// identifier-bound clarification; later fit rejection is too late.
|
|
651
853
|
eligible: unrequestedStaticScope ? false : candidate.eligible,
|
|
652
|
-
compatibility:
|
|
854
|
+
compatibility: completeCertifiedFit
|
|
653
855
|
? 'compatible'
|
|
654
856
|
: fit?.action === 'rejected_for_fit'
|
|
655
857
|
? 'incompatible'
|
|
@@ -716,6 +918,7 @@ function trustTierFor(object, evidenceClass) {
|
|
|
716
918
|
function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases, relevanceReasons, ambiguityPeerIds) {
|
|
717
919
|
const payload = object.payload ?? {};
|
|
718
920
|
const analyticalCapability = normalizeMetricCapabilityContract(payload.analyticalCapability);
|
|
921
|
+
const relationshipSafety = relationshipSafetyFromObject(object);
|
|
719
922
|
const qualifiedId = firstString(payload.qualifiedId, payload.uniqueId, object.fullName, object.objectKey) ?? object.objectKey;
|
|
720
923
|
const parameters = arrayNames(payload.parameters ?? payload.parameterPolicy);
|
|
721
924
|
const outputs = uniqueStrings([
|
|
@@ -723,7 +926,20 @@ function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases
|
|
|
723
926
|
...arrayNames(payload.outputContract),
|
|
724
927
|
...arrayNames(payload.outputs),
|
|
725
928
|
]);
|
|
929
|
+
const declaredSemanticGeography = [
|
|
930
|
+
...stringArray(payload.semanticRoles),
|
|
931
|
+
...stringArray(payload.semantic_roles),
|
|
932
|
+
firstString(payload.semanticRole, payload.semantic_role) ?? '',
|
|
933
|
+
].some((role) => {
|
|
934
|
+
const normalized = normalizeText(role);
|
|
935
|
+
return normalized === 'geography' || normalized === 'geographic';
|
|
936
|
+
});
|
|
726
937
|
const compatibilityFacts = uniqueStrings([
|
|
938
|
+
// Carry only authored, snapshot-local compatibility facts. Consumers use
|
|
939
|
+
// these as typed declarations, never as fuzzy lexical aliases.
|
|
940
|
+
...stringArray(payload.compatibilityFacts),
|
|
941
|
+
...stringArray(payload.compatibility_facts),
|
|
942
|
+
...(declaredSemanticGeography ? ['semantic-role: geography'] : []),
|
|
727
943
|
firstString(payload.grain) ? `grain: ${firstString(payload.grain)}` : '',
|
|
728
944
|
...stringArray(payload.dimensions).slice(0, 8).map((value) => `dimension: ${value}`),
|
|
729
945
|
...parameters.slice(0, 8).map((value) => `parameter: ${value}`),
|
|
@@ -768,6 +984,7 @@ function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases
|
|
|
768
984
|
]).slice(0, 12),
|
|
769
985
|
},
|
|
770
986
|
...(analyticalCapability ? { analyticalCapability } : {}),
|
|
987
|
+
...(relationshipSafety ? { relationshipSafety: [relationshipSafety] } : {}),
|
|
771
988
|
provenance: firstString(payload.provenance, object.sourceSystem),
|
|
772
989
|
ambiguityPeerIds,
|
|
773
990
|
};
|
|
@@ -907,6 +1124,11 @@ function stringArray(value) {
|
|
|
907
1124
|
? value.filter((item) => typeof item === 'string' && Boolean(item.trim()))
|
|
908
1125
|
: [];
|
|
909
1126
|
}
|
|
1127
|
+
function recordValue(value) {
|
|
1128
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
1129
|
+
? value
|
|
1130
|
+
: undefined;
|
|
1131
|
+
}
|
|
910
1132
|
function arrayNames(value) {
|
|
911
1133
|
if (!Array.isArray(value))
|
|
912
1134
|
return [];
|