@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.
Files changed (173) hide show
  1. package/dist/agent-run-engine.d.ts +68 -2
  2. package/dist/agent-run-engine.d.ts.map +1 -1
  3. package/dist/agent-run-engine.js +520 -26
  4. package/dist/agent-run-engine.js.map +1 -1
  5. package/dist/agent-run-gates.d.ts.map +1 -1
  6. package/dist/agent-run-gates.js +25 -0
  7. package/dist/agent-run-gates.js.map +1 -1
  8. package/dist/agentic/analyst-loop.d.ts +91 -0
  9. package/dist/agentic/analyst-loop.d.ts.map +1 -0
  10. package/dist/agentic/analyst-loop.js +331 -0
  11. package/dist/agentic/analyst-loop.js.map +1 -0
  12. package/dist/agentic/assumptions.d.ts +62 -0
  13. package/dist/agentic/assumptions.d.ts.map +1 -0
  14. package/dist/agentic/assumptions.js +73 -0
  15. package/dist/agentic/assumptions.js.map +1 -0
  16. package/dist/agentic/business-explanation.d.ts +56 -0
  17. package/dist/agentic/business-explanation.d.ts.map +1 -0
  18. package/dist/agentic/business-explanation.js +130 -0
  19. package/dist/agentic/business-explanation.js.map +1 -0
  20. package/dist/agentic/identifier-ledger.d.ts +74 -0
  21. package/dist/agentic/identifier-ledger.d.ts.map +1 -0
  22. package/dist/agentic/identifier-ledger.js +151 -0
  23. package/dist/agentic/identifier-ledger.js.map +1 -0
  24. package/dist/agentic/ledger-tools.d.ts +68 -0
  25. package/dist/agentic/ledger-tools.d.ts.map +1 -0
  26. package/dist/agentic/ledger-tools.js +198 -0
  27. package/dist/agentic/ledger-tools.js.map +1 -0
  28. package/dist/agentic/orchestrator-policy.d.ts +60 -0
  29. package/dist/agentic/orchestrator-policy.d.ts.map +1 -0
  30. package/dist/agentic/orchestrator-policy.js +73 -0
  31. package/dist/agentic/orchestrator-policy.js.map +1 -0
  32. package/dist/agentic/orchestrator.d.ts +54 -0
  33. package/dist/agentic/orchestrator.d.ts.map +1 -0
  34. package/dist/agentic/orchestrator.js +67 -0
  35. package/dist/agentic/orchestrator.js.map +1 -0
  36. package/dist/agentic/preview-tool.d.ts +36 -0
  37. package/dist/agentic/preview-tool.d.ts.map +1 -0
  38. package/dist/agentic/preview-tool.js +71 -0
  39. package/dist/agentic/preview-tool.js.map +1 -0
  40. package/dist/agentic/rerank.d.ts +50 -0
  41. package/dist/agentic/rerank.d.ts.map +1 -0
  42. package/dist/agentic/rerank.js +140 -0
  43. package/dist/agentic/rerank.js.map +1 -0
  44. package/dist/agentic/research/hypothesis.d.ts +110 -0
  45. package/dist/agentic/research/hypothesis.d.ts.map +1 -0
  46. package/dist/agentic/research/hypothesis.js +125 -0
  47. package/dist/agentic/research/hypothesis.js.map +1 -0
  48. package/dist/agentic/research/synthesis.d.ts +36 -0
  49. package/dist/agentic/research/synthesis.d.ts.map +1 -0
  50. package/dist/agentic/research/synthesis.js +109 -0
  51. package/dist/agentic/research/synthesis.js.map +1 -0
  52. package/dist/agentic/research-agent.d.ts +54 -0
  53. package/dist/agentic/research-agent.d.ts.map +1 -0
  54. package/dist/agentic/research-agent.js +177 -0
  55. package/dist/agentic/research-agent.js.map +1 -0
  56. package/dist/agentic/sql-authorization.d.ts +131 -0
  57. package/dist/agentic/sql-authorization.d.ts.map +1 -0
  58. package/dist/agentic/sql-authorization.js +460 -0
  59. package/dist/agentic/sql-authorization.js.map +1 -0
  60. package/dist/agentic/tool-loop.d.ts +29 -0
  61. package/dist/agentic/tool-loop.d.ts.map +1 -1
  62. package/dist/agentic/tool-loop.js +129 -52
  63. package/dist/agentic/tool-loop.js.map +1 -1
  64. package/dist/agentic/toolset.d.ts.map +1 -1
  65. package/dist/agentic/toolset.js +155 -0
  66. package/dist/agentic/toolset.js.map +1 -1
  67. package/dist/agentic/turn-plan.d.ts +35 -0
  68. package/dist/agentic/turn-plan.d.ts.map +1 -0
  69. package/dist/agentic/turn-plan.js +108 -0
  70. package/dist/agentic/turn-plan.js.map +1 -0
  71. package/dist/agentic/value-probe.d.ts +89 -0
  72. package/dist/agentic/value-probe.d.ts.map +1 -0
  73. package/dist/agentic/value-probe.js +304 -0
  74. package/dist/agentic/value-probe.js.map +1 -0
  75. package/dist/analytical-frame.d.ts.map +1 -1
  76. package/dist/analytical-frame.js +10 -1
  77. package/dist/analytical-frame.js.map +1 -1
  78. package/dist/analytical-orchestration.d.ts +382 -0
  79. package/dist/analytical-orchestration.d.ts.map +1 -1
  80. package/dist/analytical-orchestration.js +782 -6
  81. package/dist/analytical-orchestration.js.map +1 -1
  82. package/dist/analytical-request-policy.d.ts +26 -0
  83. package/dist/analytical-request-policy.d.ts.map +1 -0
  84. package/dist/analytical-request-policy.js +82 -0
  85. package/dist/analytical-request-policy.js.map +1 -0
  86. package/dist/analytical-result-facts.d.ts +13 -0
  87. package/dist/analytical-result-facts.d.ts.map +1 -1
  88. package/dist/analytical-result-facts.js +37 -1
  89. package/dist/analytical-result-facts.js.map +1 -1
  90. package/dist/answer-loop.d.ts +105 -16
  91. package/dist/answer-loop.d.ts.map +1 -1
  92. package/dist/answer-loop.js +833 -108
  93. package/dist/answer-loop.js.map +1 -1
  94. package/dist/answer-shape.d.ts +32 -1
  95. package/dist/answer-shape.d.ts.map +1 -1
  96. package/dist/answer-shape.js +49 -3
  97. package/dist/answer-shape.js.map +1 -1
  98. package/dist/cascade/budgets.d.ts.map +1 -1
  99. package/dist/cascade/budgets.js +8 -1
  100. package/dist/cascade/budgets.js.map +1 -1
  101. package/dist/cascade/packer.d.ts +88 -0
  102. package/dist/cascade/packer.d.ts.map +1 -0
  103. package/dist/cascade/packer.js +106 -0
  104. package/dist/cascade/packer.js.map +1 -0
  105. package/dist/conversation/result-ops.d.ts +18 -3
  106. package/dist/conversation/result-ops.d.ts.map +1 -1
  107. package/dist/conversation/result-ops.js +77 -5
  108. package/dist/conversation/result-ops.js.map +1 -1
  109. package/dist/conversation/rolling-summary.d.ts.map +1 -1
  110. package/dist/conversation/rolling-summary.js +7 -2
  111. package/dist/conversation/rolling-summary.js.map +1 -1
  112. package/dist/conversation/session-store.d.ts +3 -0
  113. package/dist/conversation/session-store.d.ts.map +1 -1
  114. package/dist/conversation/session-store.js +8 -4
  115. package/dist/conversation/session-store.js.map +1 -1
  116. package/dist/conversation/snapshot.d.ts +20 -0
  117. package/dist/conversation/snapshot.d.ts.map +1 -1
  118. package/dist/conversation/snapshot.js +40 -2
  119. package/dist/conversation/snapshot.js.map +1 -1
  120. package/dist/fixtures/ask-ai-office-shaped.d.ts +153 -0
  121. package/dist/fixtures/ask-ai-office-shaped.d.ts.map +1 -0
  122. package/dist/fixtures/ask-ai-office-shaped.js +94 -0
  123. package/dist/fixtures/ask-ai-office-shaped.js.map +1 -0
  124. package/dist/hints/store.d.ts.map +1 -1
  125. package/dist/hints/store.js +15 -5
  126. package/dist/hints/store.js.map +1 -1
  127. package/dist/index.d.ts +29 -9
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.js +29 -6
  130. package/dist/index.js.map +1 -1
  131. package/dist/intent-controller.d.ts +36 -0
  132. package/dist/intent-controller.d.ts.map +1 -1
  133. package/dist/intent-controller.js +95 -0
  134. package/dist/intent-controller.js.map +1 -1
  135. package/dist/meaning-resolution.d.ts +69 -2
  136. package/dist/meaning-resolution.d.ts.map +1 -1
  137. package/dist/meaning-resolution.js +128 -7
  138. package/dist/meaning-resolution.js.map +1 -1
  139. package/dist/metadata/analysis-planner.d.ts.map +1 -1
  140. package/dist/metadata/analysis-planner.js +124 -7
  141. package/dist/metadata/analysis-planner.js.map +1 -1
  142. package/dist/metadata/block-fit.d.ts +36 -0
  143. package/dist/metadata/block-fit.d.ts.map +1 -1
  144. package/dist/metadata/block-fit.js +290 -38
  145. package/dist/metadata/block-fit.js.map +1 -1
  146. package/dist/metadata/catalog.d.ts +20 -23
  147. package/dist/metadata/catalog.d.ts.map +1 -1
  148. package/dist/metadata/catalog.js +193 -22
  149. package/dist/metadata/catalog.js.map +1 -1
  150. package/dist/metadata/meaning-evidence.d.ts +10 -1
  151. package/dist/metadata/meaning-evidence.d.ts.map +1 -1
  152. package/dist/metadata/meaning-evidence.js +275 -53
  153. package/dist/metadata/meaning-evidence.js.map +1 -1
  154. package/dist/metadata/metric-match.d.ts +32 -0
  155. package/dist/metadata/metric-match.d.ts.map +1 -1
  156. package/dist/metadata/metric-match.js +120 -9
  157. package/dist/metadata/metric-match.js.map +1 -1
  158. package/dist/research-loop.d.ts +33 -2
  159. package/dist/research-loop.d.ts.map +1 -1
  160. package/dist/research-loop.js +66 -4
  161. package/dist/research-loop.js.map +1 -1
  162. package/dist/resolved-analytical-plan.d.ts +7 -0
  163. package/dist/resolved-analytical-plan.d.ts.map +1 -1
  164. package/dist/resolved-analytical-plan.js +11 -4
  165. package/dist/resolved-analytical-plan.js.map +1 -1
  166. package/dist/router.d.ts +32 -19
  167. package/dist/router.d.ts.map +1 -1
  168. package/dist/router.js +1977 -73
  169. package/dist/router.js.map +1 -1
  170. package/dist/semantic-bridge/member-select.d.ts.map +1 -1
  171. package/dist/semantic-bridge/member-select.js +39 -21
  172. package/dist/semantic-bridge/member-select.js.map +1 -1
  173. 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 { AgentRetrievalEvidence } from '../meaning-resolution.js';
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":"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"}
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* the identities. `compileGovernedRelationalPlan` still
118
- * enforces certified, fresh, automatic-join-safe (`REL-002`) before it emits a
119
- * join, so surfacing a draft relationship here cannot authorize one.
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 identitiesByEntity = new Map();
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
- for (const alias of entityAliasesForRelationshipMatch(endpoint)) {
136
- const existing = identitiesByEntity.get(alias) ?? new Set();
137
- for (const identity of identities)
138
- existing.add(identity);
139
- identitiesByEntity.set(alias, existing);
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 (identitiesByEntity.size === 0)
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
- for (const reference of [dimension.entityId, ...(dimension.nativeGroupingPath ?? [])]) {
149
- for (const alias of entityAliasesForRelationshipMatch(reference)) {
150
- for (const identity of identitiesByEntity.get(alias) ?? [])
151
- pathRelationships.add(identity);
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 capabilityPathEntities = candidate.analyticalCapability?.dimensions.flatMap((dimension) => [
159
- dimension.entityId,
160
- ...(dimension.nativeGroupingPath ?? []),
161
- ]) ?? [];
162
- for (const reference of [
163
- candidate.primaryEntity,
164
- candidate.analyticalCapability?.primaryEntityId,
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 maxRelevance = Math.max(1, ...evidence.candidates.map((candidate) => candidate.relevanceScore));
186
- const candidates = evidence.candidates.map((candidate) => agentCandidateFromMeaning(candidate, maxRelevance));
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 role = normalizeDimensionRole(object.name);
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
- || (isGeographicRole(term) && isGeographicRole(role)));
553
+ || candidateMatchesCategoricalDimensionRequirement(candidate, [term]));
361
554
  if (!relevant)
362
555
  return [];
363
- const card = candidateCard(object, 'semantic', 1, 1, aliasesFor(object), ['requested clarification capability'], []);
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) || (isGeographicRole(term) && isGeographicRole(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
- || (isGeographicRole(lane.term) && candidateTerms.some(isGeographicRole));
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
- const exactAuthoredExample = fit?.action === 'certified_answer'
637
- && pack.routeDecision.exactObjectKey === candidate.id
638
- && fit.fit.reasons.includes('question matches a certified example');
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. Only the selected block's
643
- // authored exact-example fit may grant the router shortcut.
644
- exactMatch: exactAuthoredExample,
645
- ...(exactAuthoredExample
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: fit?.action === 'certified_answer'
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 [];