@duckcodeailabs/dql-agent 1.14.1 → 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 +9 -1
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +199 -17
- 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 +12 -0
- package/dist/agent-run-gates.js.map +1 -1
- package/dist/agentic/analyst-loop.d.ts.map +1 -1
- package/dist/agentic/analyst-loop.js +48 -28
- package/dist/agentic/analyst-loop.js.map +1 -1
- package/dist/agentic/research/synthesis.d.ts +4 -0
- package/dist/agentic/research/synthesis.d.ts.map +1 -1
- package/dist/agentic/research/synthesis.js +23 -8
- package/dist/agentic/research/synthesis.js.map +1 -1
- package/dist/agentic/research-agent.d.ts.map +1 -1
- package/dist/agentic/research-agent.js +3 -2
- package/dist/agentic/research-agent.js.map +1 -1
- package/dist/agentic/sql-authorization.d.ts.map +1 -1
- package/dist/agentic/sql-authorization.js +230 -2
- package/dist/agentic/sql-authorization.js.map +1 -1
- 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 +302 -0
- package/dist/analytical-orchestration.d.ts.map +1 -1
- package/dist/analytical-orchestration.js +564 -0
- package/dist/analytical-orchestration.js.map +1 -1
- package/dist/answer-loop.d.ts +48 -0
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +679 -85
- 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/conversation/snapshot.d.ts +20 -0
- package/dist/conversation/snapshot.d.ts.map +1 -1
- package/dist/conversation/snapshot.js +30 -1
- 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/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +28 -0
- package/dist/intent-controller.d.ts.map +1 -1
- package/dist/intent-controller.js +30 -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 +18 -0
- package/dist/metadata/block-fit.d.ts.map +1 -1
- package/dist/metadata/block-fit.js +250 -38
- package/dist/metadata/block-fit.js.map +1 -1
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +57 -11
- 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 +261 -47
- package/dist/metadata/meaning-evidence.js.map +1 -1
- package/dist/research-loop.d.ts.map +1 -1
- package/dist/research-loop.js +23 -1
- 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 +4 -3
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +1796 -125
- 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
|
|
@@ -640,9 +831,12 @@ export function applyContextPackCompatibility(evidence, pack, selectedEvidenceId
|
|
|
640
831
|
// `monthly_revenue` artifact lose to a semantic metric for the ordinary
|
|
641
832
|
// paraphrase "What is monthly revenue?". Do not promote lexical
|
|
642
833
|
// relevance alone; require the catalog's executable fit verdict.
|
|
834
|
+
const requestedMeasures = pack.questionPlan.requestedShape.measures;
|
|
835
|
+
const blockDeclaresRequestedMeasures = certifiedCandidateExplicitlyCoversMeasures(candidate, requestedMeasures);
|
|
643
836
|
const completeCertifiedFit = fit?.action === 'certified_answer'
|
|
644
837
|
&& (fit.fit.kind === 'exact' || fit.fit.kind === 'trim_safe')
|
|
645
|
-
&& fit.fit.confidence === 'high'
|
|
838
|
+
&& fit.fit.confidence === 'high'
|
|
839
|
+
&& blockDeclaresRequestedMeasures;
|
|
646
840
|
return {
|
|
647
841
|
...candidate,
|
|
648
842
|
// Lexical equality to a block name/alias is useful retrieval signal,
|
|
@@ -657,7 +851,7 @@ export function applyContextPackCompatibility(evidence, pack, selectedEvidenceId
|
|
|
657
851
|
// broader question. Exclude it before both meaning resolution and
|
|
658
852
|
// identifier-bound clarification; later fit rejection is too late.
|
|
659
853
|
eligible: unrequestedStaticScope ? false : candidate.eligible,
|
|
660
|
-
compatibility:
|
|
854
|
+
compatibility: completeCertifiedFit
|
|
661
855
|
? 'compatible'
|
|
662
856
|
: fit?.action === 'rejected_for_fit'
|
|
663
857
|
? 'incompatible'
|
|
@@ -724,6 +918,7 @@ function trustTierFor(object, evidenceClass) {
|
|
|
724
918
|
function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases, relevanceReasons, ambiguityPeerIds) {
|
|
725
919
|
const payload = object.payload ?? {};
|
|
726
920
|
const analyticalCapability = normalizeMetricCapabilityContract(payload.analyticalCapability);
|
|
921
|
+
const relationshipSafety = relationshipSafetyFromObject(object);
|
|
727
922
|
const qualifiedId = firstString(payload.qualifiedId, payload.uniqueId, object.fullName, object.objectKey) ?? object.objectKey;
|
|
728
923
|
const parameters = arrayNames(payload.parameters ?? payload.parameterPolicy);
|
|
729
924
|
const outputs = uniqueStrings([
|
|
@@ -731,7 +926,20 @@ function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases
|
|
|
731
926
|
...arrayNames(payload.outputContract),
|
|
732
927
|
...arrayNames(payload.outputs),
|
|
733
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
|
+
});
|
|
734
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'] : []),
|
|
735
943
|
firstString(payload.grain) ? `grain: ${firstString(payload.grain)}` : '',
|
|
736
944
|
...stringArray(payload.dimensions).slice(0, 8).map((value) => `dimension: ${value}`),
|
|
737
945
|
...parameters.slice(0, 8).map((value) => `parameter: ${value}`),
|
|
@@ -776,6 +984,7 @@ function candidateCard(object, evidenceClass, classRank, relevanceScore, aliases
|
|
|
776
984
|
]).slice(0, 12),
|
|
777
985
|
},
|
|
778
986
|
...(analyticalCapability ? { analyticalCapability } : {}),
|
|
987
|
+
...(relationshipSafety ? { relationshipSafety: [relationshipSafety] } : {}),
|
|
779
988
|
provenance: firstString(payload.provenance, object.sourceSystem),
|
|
780
989
|
ambiguityPeerIds,
|
|
781
990
|
};
|
|
@@ -915,6 +1124,11 @@ function stringArray(value) {
|
|
|
915
1124
|
? value.filter((item) => typeof item === 'string' && Boolean(item.trim()))
|
|
916
1125
|
: [];
|
|
917
1126
|
}
|
|
1127
|
+
function recordValue(value) {
|
|
1128
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
1129
|
+
? value
|
|
1130
|
+
: undefined;
|
|
1131
|
+
}
|
|
918
1132
|
function arrayNames(value) {
|
|
919
1133
|
if (!Array.isArray(value))
|
|
920
1134
|
return [];
|