@duckcodeailabs/dql-mcp 1.6.20 → 1.6.22

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.
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Grounded-SQL metadata tools (spec 15.5) — thin wrappers over the dbt catalog
3
+ * + the shared sql-grounding so EXTERNAL agents look up the same context the
4
+ * notebook build path uses:
5
+ *
6
+ * - `search_metadata` — rank dbt tables relevant to a request, with their
7
+ * qualified relation + {{ ref() }} form.
8
+ * - `get_table_schema` — the qualified relation, {{ ref() }} form, real
9
+ * columns + types, and join keys for a table.
10
+ * - `validate_sql` — validate a SQL string references ONLY known
11
+ * relations/columns, returning the precise miss.
12
+ *
13
+ * All three load dbt artifacts read-only and never write. They degrade
14
+ * gracefully (a clear hint) when no dbt manifest is present.
15
+ */
16
+ import { z } from 'zod';
17
+ import { resolveDbtManifestPath } from '@duckcodeailabs/dql-core';
18
+ import { buildSchemaGrounding, loadDbtArtifacts, relationKeys, selectRelevantModels, validateSqlAgainstGrounding, } from '@duckcodeailabs/dql-agent';
19
+ /** Load dbt artifacts for the project, or undefined when no manifest exists. */
20
+ function loadArtifacts(ctx) {
21
+ const manifestPath = resolveDbtManifestPath(ctx.projectRoot);
22
+ if (!manifestPath)
23
+ return undefined;
24
+ try {
25
+ return loadDbtArtifacts(manifestPath);
26
+ }
27
+ catch {
28
+ return undefined;
29
+ }
30
+ }
31
+ const NO_MANIFEST_HINT = 'No dbt manifest was found. Run `dbt compile`/`dbt build` (or `dql sync dbt`) so target/manifest.json exists.';
32
+ // ─── search_metadata ──────────────────────────────────────────────────────────
33
+ export const searchMetadataInput = {
34
+ query: z.string().describe('Natural-language request to find relevant tables for.'),
35
+ limit: z.number().int().min(1).max(40).optional().describe('Max tables to return (default 12).'),
36
+ };
37
+ export async function searchMetadata(ctx, args) {
38
+ const artifacts = loadArtifacts(ctx);
39
+ if (!artifacts)
40
+ return { tables: [], hint: NO_MANIFEST_HINT };
41
+ const topK = args.limit ?? 12;
42
+ const relevant = await selectRelevantModels(artifacts, args.query, { topK });
43
+ const grounding = buildSchemaGrounding(artifacts, relevant, { limit: topK });
44
+ return {
45
+ total: grounding.tables.length,
46
+ tables: grounding.tables.map((table) => ({
47
+ name: table.name,
48
+ qualifiedRelation: table.qualifiedRelation,
49
+ refForm: table.refForm ?? null,
50
+ kind: table.kind,
51
+ columnCount: table.columns.length,
52
+ certifiedBlock: table.certifiedBlock ?? false,
53
+ })),
54
+ };
55
+ }
56
+ // ─── get_table_schema ─────────────────────────────────────────────────────────
57
+ export const getTableSchemaInput = {
58
+ table: z.string().describe('Model name, alias, or qualified relation (e.g. order_items or dev.order_items).'),
59
+ };
60
+ export function getTableSchema(ctx, args) {
61
+ const artifacts = loadArtifacts(ctx);
62
+ if (!artifacts)
63
+ return { found: false, hint: NO_MANIFEST_HINT };
64
+ // Ground the whole project so we can look up any relation and surface joins.
65
+ const grounding = buildSchemaGrounding(artifacts, undefined, { limit: 500 });
66
+ const keys = relationKeys(args.table);
67
+ const table = keys.map((key) => grounding.byKey.get(key)).find(Boolean);
68
+ if (!table) {
69
+ return {
70
+ found: false,
71
+ hint: `Table "${args.table}" was not found. Use search_metadata to discover available relations.`,
72
+ };
73
+ }
74
+ const joinKeys = grounding.joinKeys.filter((join) => join.leftRelation === table.qualifiedRelation || join.rightRelation === table.qualifiedRelation);
75
+ return {
76
+ found: true,
77
+ name: table.name,
78
+ qualifiedRelation: table.qualifiedRelation,
79
+ refForm: table.refForm ?? null,
80
+ kind: table.kind,
81
+ certifiedBlock: table.certifiedBlock ?? false,
82
+ columns: table.columns.map((column) => ({ name: column.name, type: column.type ?? null, description: column.description ?? null })),
83
+ joinKeys: joinKeys.map((join) => ({
84
+ leftRelation: join.leftRelation,
85
+ leftColumn: join.leftColumn,
86
+ rightRelation: join.rightRelation,
87
+ rightColumn: join.rightColumn,
88
+ reason: join.reason,
89
+ })),
90
+ };
91
+ }
92
+ // ─── validate_sql ─────────────────────────────────────────────────────────────
93
+ export const validateSqlInput = {
94
+ sql: z.string().describe('A read-only SELECT/WITH query to validate against the dbt schema.'),
95
+ query: z
96
+ .string()
97
+ .optional()
98
+ .describe('Optional original request, used to scope the grounding to relevant tables.'),
99
+ };
100
+ export async function validateSql(ctx, args) {
101
+ const artifacts = loadArtifacts(ctx);
102
+ if (!artifacts) {
103
+ return { ok: false, code: 'no_schema', error: NO_MANIFEST_HINT, warnings: [] };
104
+ }
105
+ // Ground broadly (so a valid table is never wrongly flagged), optionally
106
+ // ordered by the original request.
107
+ const relevant = args.query ? await selectRelevantModels(artifacts, args.query, { topK: 500 }) : undefined;
108
+ const grounding = buildSchemaGrounding(artifacts, relevant, { limit: 500 });
109
+ const result = validateSqlAgainstGrounding(args.sql, grounding);
110
+ if (result.ok) {
111
+ return { ok: true, warnings: result.warnings, referencedRelations: result.referencedRelations };
112
+ }
113
+ return {
114
+ ok: false,
115
+ code: result.code,
116
+ error: result.error,
117
+ warnings: result.warnings,
118
+ referencedRelations: result.referencedRelations,
119
+ offending: result.offending ?? null,
120
+ };
121
+ }
122
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../src/tools/metadata.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
1
  import { z } from 'zod';
2
+ import type { InvariantResult } from '@duckcodeailabs/dql-governance';
2
3
  import type { DQLContext } from '../context.js';
3
4
  export declare const queryViaBlockInput: {
4
5
  name: z.ZodString;
5
6
  limit: z.ZodOptional<z.ZodNumber>;
7
+ question: z.ZodOptional<z.ZodString>;
6
8
  serverUrl: z.ZodOptional<z.ZodString>;
7
9
  };
8
10
  /**
@@ -13,18 +15,22 @@ export declare const queryViaBlockInput: {
13
15
  export declare function queryViaBlock(ctx: DQLContext, args: {
14
16
  name: string;
15
17
  limit?: number;
18
+ question?: string;
16
19
  serverUrl?: string;
17
20
  }): Promise<{
18
21
  error: string;
19
- block?: undefined;
20
- blockPath?: undefined;
21
- rowCount?: undefined;
22
- durationMs?: undefined;
23
- columns?: undefined;
24
- rows?: undefined;
22
+ routeReason?: undefined;
23
+ grainGate?: undefined;
24
+ } | {
25
+ error: string;
26
+ routeReason: string;
27
+ grainGate: {
28
+ allow: false;
29
+ kind: import("@duckcodeailabs/dql-agent").GrainGateKind;
30
+ requestedGrain: string;
31
+ blockGrain: string;
32
+ };
25
33
  } | {
26
- block: string;
27
- blockPath: string;
28
34
  rowCount: number;
29
35
  durationMs: number | null;
30
36
  columns: {
@@ -32,6 +38,15 @@ export declare function queryViaBlock(ctx: DQLContext, args: {
32
38
  type?: string;
33
39
  }[];
34
40
  rows: unknown[];
41
+ invariantResults?: InvariantResult[] | undefined;
42
+ dataStateDetail?: string | undefined;
43
+ block: string;
44
+ blockPath: string;
45
+ trustLabel: string;
46
+ invariantViolation: boolean;
47
+ dataState: "fresh" | "stale" | "failed" | "unknown";
35
48
  error?: undefined;
49
+ routeReason?: undefined;
50
+ grainGate?: undefined;
36
51
  }>;
37
52
  //# sourceMappingURL=query-via-block.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"query-via-block.d.ts","sourceRoot":"","sources":["../../src/tools/query-via-block.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,eAAO,MAAM,kBAAkB;;;;CAS9B,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,EACf,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;cA+D9B,MAAM;eAAS,MAAM;;;;GAiBlD"}
1
+ {"version":3,"file":"query-via-block.d.ts","sourceRoot":"","sources":["../../src/tools/query-via-block.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAYtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,eAAO,MAAM,kBAAkB;;;;;CAe9B,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,EACf,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;cAuFjD,MAAM;eAAS,MAAM;;;;;;;;;;;;;GAmDlD"}
@@ -1,9 +1,15 @@
1
1
  import { z } from 'zod';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
+ import { buildAnalysisQuestionPlan, grainMatches, requestedGrainFromPlan, } from '@duckcodeailabs/dql-agent';
5
+ import { composeEffectiveTrust, TRUST_QUALIFIER_INVARIANT_VIOLATED, } from '@duckcodeailabs/dql-core';
4
6
  export const queryViaBlockInput = {
5
7
  name: z.string().describe('Certified block to execute.'),
6
8
  limit: z.number().int().min(1).max(10000).optional().describe('Max rows to return.'),
9
+ question: z
10
+ .string()
11
+ .optional()
12
+ .describe('Original question this block is being served for. When provided, query_via_block re-checks the block grain against the requested grain (defense in depth) and refuses on a genuine grain mismatch.'),
7
13
  serverUrl: z
8
14
  .string()
9
15
  .optional()
@@ -23,6 +29,29 @@ export async function queryViaBlock(ctx, args) {
23
29
  error: `Block "${args.name}" is "${block.status ?? 'draft'}" — only certified blocks can be executed via MCP.`,
24
30
  };
25
31
  }
32
+ // Grain-gate defense in depth. When a question is provided, re-run the same
33
+ // grain check the router uses before serving this certified block. A genuine
34
+ // grain/entity mismatch is refused here even if the tool was called directly,
35
+ // so a near-miss certified block can never be served as a confidently-wrong
36
+ // governed answer. Behavior is unchanged when no question is supplied or when
37
+ // the block / question carries no clearly-extractable grain.
38
+ if (args.question) {
39
+ const plan = buildAnalysisQuestionPlan(args.question);
40
+ const requestedGrain = requestedGrainFromPlan(plan);
41
+ const gate = grainMatches(blockToGrainObject(block), requestedGrain);
42
+ if (!gate.allow) {
43
+ return {
44
+ error: `Block "${args.name}" failed the grain gate: ${gate.reason}. Refusing to serve a near-miss certified answer; generate SQL for the requested grain instead.`,
45
+ routeReason: gate.reason,
46
+ grainGate: {
47
+ allow: gate.allow,
48
+ kind: gate.kind,
49
+ requestedGrain: gate.requestedGrainLabel,
50
+ blockGrain: gate.blockGrainLabel,
51
+ },
52
+ };
53
+ }
54
+ }
26
55
  // v1.6 — DataLex contract enforcement (the wedge).
27
56
  // When a certified block declares datalex_contract, refuse the call if
28
57
  // the reference doesn't resolve in the project's loaded DataLex
@@ -76,13 +105,60 @@ export async function queryViaBlock(ctx, args) {
76
105
  if (payload.error)
77
106
  return { error: payload.error };
78
107
  const rows = payload.result?.rows ?? [];
108
+ // Invariant enforcement: the runtime evaluates the block's declared
109
+ // invariants against this run's result. A real violation downgrades the
110
+ // trust label even though the block is certified — "certified" means the
111
+ // logic was reviewed, not that today's data honors every stated guarantee.
112
+ const invariantResults = payload.invariantResults ?? [];
113
+ const invariantViolation = Boolean(payload.invariantViolation);
114
+ const declaredInvariants = block.invariants ?? [];
115
+ // Freshness-aware trust: "certified" is the logic axis; the data axis is the
116
+ // health of the block's upstream dbt models (last-run status + freshness),
117
+ // computed at build time into `block.dataState`. A certified block whose
118
+ // upstream failed/is stale is downgraded to "Certified · upstream failed" /
119
+ // "Certified · stale data". An invariant violation is a stronger signal, so
120
+ // it keeps the qualifier slot when both apply (composeEffectiveTrust honors
121
+ // the existing qualifier first). Missing run_results → undefined dataState →
122
+ // plain "Certified" (backward compatible).
123
+ const dataState = block.dataState;
124
+ const effectiveTrust = composeEffectiveTrust({
125
+ id: 'certified',
126
+ dataState,
127
+ existingQualifier: invariantViolation ? TRUST_QUALIFIER_INVARIANT_VIOLATED : undefined,
128
+ });
79
129
  return {
80
130
  block: args.name,
81
131
  blockPath: block.filePath,
132
+ trustLabel: effectiveTrust.display,
133
+ invariantViolation,
134
+ dataState: dataState ?? 'unknown',
135
+ ...(block.dataStateDetail
136
+ ? { dataStateDetail: block.dataStateDetail }
137
+ : {}),
138
+ ...(declaredInvariants.length > 0 || invariantResults.length > 0
139
+ ? { invariantResults }
140
+ : {}),
82
141
  rowCount: rows.length,
83
142
  durationMs: payload.result?.executionTime ?? null,
84
143
  columns: payload.result?.columns ?? [],
85
144
  rows: args.limit ? rows.slice(0, args.limit) : rows,
86
145
  };
87
146
  }
147
+ /**
148
+ * Adapt a manifest block into the minimal `MetadataObject` shape the grain gate
149
+ * reads (it only inspects `payload.grain`, `payload.declaredOutputs`, and
150
+ * `payload.entities`).
151
+ */
152
+ function blockToGrainObject(block) {
153
+ return {
154
+ objectKey: `dql:block:${block.name}`,
155
+ objectType: 'dql_block',
156
+ name: block.name,
157
+ payload: {
158
+ grain: block.grain,
159
+ declaredOutputs: block.declaredOutputs ?? [],
160
+ entities: block.entities ?? [],
161
+ },
162
+ };
163
+ }
88
164
  //# sourceMappingURL=query-via-block.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"query-via-block.js","sourceRoot":"","sources":["../../src/tools/query-via-block.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"query-via-block.js","sourceRoot":"","sources":["../../src/tools/query-via-block.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import type { DQLContext } from '../context.js';
3
3
  import { type LocalContextPack } from '@duckcodeailabs/dql-agent';
4
+ import type { DataLexConformance, DataLexRelationship, JoinPathResolution } from '@duckcodeailabs/dql-core';
4
5
  export declare const queryViaMetadataInput: {
5
6
  question: z.ZodString;
6
7
  proposedSql: z.ZodOptional<z.ZodString>;
@@ -70,6 +71,7 @@ export declare function queryViaMetadata(ctx: DQLContext, args: {
70
71
  reviewStatus: string;
71
72
  planningOnly: boolean;
72
73
  contextPack: LocalContextPack | undefined;
74
+ datalexJoinGuidance: DataLexJoinGuidance | undefined;
73
75
  routeDecision: import("@duckcodeailabs/dql-agent").MetadataRouteDecision | undefined;
74
76
  allowedSqlContext: import("@duckcodeailabs/dql-agent").MetadataAllowedSqlContext | undefined;
75
77
  selectedRelations: {
@@ -257,6 +259,7 @@ export declare function queryViaMetadata(ctx: DQLContext, args: {
257
259
  proposedSql: string;
258
260
  draftBlock: undefined;
259
261
  planningOnly?: undefined;
262
+ datalexJoinGuidance?: undefined;
260
263
  routeDecision?: undefined;
261
264
  allowedSqlContext?: undefined;
262
265
  selectedRelations?: undefined;
@@ -348,6 +351,7 @@ export declare function queryViaMetadata(ctx: DQLContext, args: {
348
351
  assumptions: string[];
349
352
  };
350
353
  contextPack: LocalContextPack | undefined;
354
+ datalexJoinGuidance: DataLexJoinGuidance | undefined;
351
355
  reason: string;
352
356
  proposedSql: string;
353
357
  draftBlock: {
@@ -457,6 +461,7 @@ export declare function queryViaMetadata(ctx: DQLContext, args: {
457
461
  validationWarnings: string[];
458
462
  reviewStatus?: undefined;
459
463
  planningOnly?: undefined;
464
+ datalexJoinGuidance?: undefined;
460
465
  routeDecision?: undefined;
461
466
  allowedSqlContext?: undefined;
462
467
  selectedRelations?: undefined;
@@ -550,6 +555,7 @@ export declare function queryViaMetadata(ctx: DQLContext, args: {
550
555
  assumptions: string[];
551
556
  };
552
557
  contextPack: LocalContextPack | undefined;
558
+ datalexJoinGuidance: DataLexJoinGuidance | undefined;
553
559
  reason: string;
554
560
  question: string;
555
561
  rowCount: number;
@@ -577,6 +583,54 @@ export declare function queryViaMetadata(ctx: DQLContext, args: {
577
583
  errorCode?: undefined;
578
584
  error?: undefined;
579
585
  }>;
586
+ export interface DataLexJoinGuidance {
587
+ source: 'datalex_manifest';
588
+ note: string;
589
+ entities: Array<{
590
+ concept: string;
591
+ domain?: string;
592
+ canonicalKey?: string[];
593
+ physical: string[];
594
+ }>;
595
+ joins: Array<{
596
+ from: string;
597
+ to: string;
598
+ on?: string;
599
+ cardinality?: string;
600
+ fansOut: boolean;
601
+ guidance: string;
602
+ }>;
603
+ unresolved?: Array<{
604
+ from: string;
605
+ to: string;
606
+ reason: string;
607
+ message: string;
608
+ }>;
609
+ }
610
+ interface JoinGuidanceRegistry {
611
+ conformance(): DataLexConformance[];
612
+ relationships(): DataLexRelationship[];
613
+ joinPath(base: string, target: string, opts?: {
614
+ baseDomain?: string;
615
+ targetDomain?: string;
616
+ }): JoinPathResolution;
617
+ }
618
+ /**
619
+ * Build grain-safe join guidance from the DataLex manifest for the entities a
620
+ * Tier-2 question touches. This is what turns conformance + typed relationships
621
+ * into something the agent can act on BEFORE it writes SQL: the canonical key to
622
+ * join each business concept on, and — for each pair — whether joining one onto
623
+ * the other fans out (and therefore needs aggregation to stay grain-safe).
624
+ *
625
+ * `refs` are the tables/entities the agent thinks are involved; physical model
626
+ * names (`dim_customer`), physical entity names (`DimCustomer`), and concept
627
+ * names (`Customer`) all resolve. With none resolved we surface the whole
628
+ * (small) conformance map so the agent still sees the modeled joins.
629
+ *
630
+ * Returns undefined when no DataLex manifest is loaded — Tier-2 must keep working
631
+ * for projects that never adopted DataLex (graduated trust).
632
+ */
633
+ export declare function buildDataLexJoinGuidance(registry: JoinGuidanceRegistry, refs: string[]): DataLexJoinGuidance | undefined;
580
634
  type MetadataResearchIntent = 'diagnose_change' | 'driver_breakdown' | 'segment_compare' | 'entity_drilldown' | 'anomaly_investigation' | 'trust_gap_review';
581
635
  type MetadataFollowUpContext = {
582
636
  kind: 'generic' | 'drilldown';
@@ -1 +1 @@
1
- {"version":3,"file":"query-via-metadata.d.ts","sourceRoot":"","sources":["../../src/tools/query-via-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,2BAA2B,CAAC;AAEnC,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiFjC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,UAAU,EACf,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,uBAAuB,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAsSsB,MAAM;4BAAc,MAAM;oCAAsB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAAtD,MAAM;4BAAc,MAAM;oCAAsB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAAtD,MAAM;4BAAc,MAAM;oCAAsB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA5NrD,MAAM;oBAAc,MAAM;4BAAsB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA4NvD,MAAM;4BAAc,MAAM;oCAAsB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA5NrD,MAAM;oBAAc,MAAM;4BAAsB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA4NvD,MAAM;4BAAc,MAAM;oCAAsB,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cApIjD,MAAM;eAAS,MAAM;;;;;cAxFzB,MAAM;oBAAc,MAAM;4BAAsB,MAAM;;;;;;;;;;;;;GA+I/E;AAID,KAAK,sBAAsB,GACvB,iBAAiB,GACjB,kBAAkB,GAClB,iBAAiB,GACjB,kBAAkB,GAClB,uBAAuB,GACvB,kBAAkB,CAAC;AAEvB,KAAK,uBAAuB,GAAG;IAC7B,IAAI,EAAE,SAAS,GAAG,WAAW,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAwLF;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQnD"}
1
+ {"version":3,"file":"query-via-metadata.d.ts","sourceRoot":"","sources":["../../src/tools/query-via-metadata.ts"],"names":[],"mappings":"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"}
@@ -89,6 +89,12 @@ export async function queryViaMetadata(ctx, args) {
89
89
  const intent = normalizeMetadataResearchIntent(args.intent, args.question);
90
90
  const contextPack = await buildTier2ContextPack(ctx.projectRoot, args.question, args.contextPackId, args.followUp);
91
91
  const hasInspectedContext = hasInspectedMetadataContext(contextPack);
92
+ // DataLex modeled join semantics (canonical keys + grain-safe orientation) for
93
+ // the entities this question touches. Undefined when no manifest is loaded.
94
+ const datalexJoinGuidance = buildDataLexJoinGuidance(ctx.datalexRegistry, [
95
+ ...(args.upstreamRefs ?? []),
96
+ ...(args.proposedEntity ? [args.proposedEntity] : []),
97
+ ]);
92
98
  const contextNeedsClarification = hasInspectedContext && contextPack?.routeDecision.route === 'clarify';
93
99
  if (contextNeedsClarification || !args.proposedSql?.trim()) {
94
100
  recordTier2QueryRun(ctx.projectRoot, {
@@ -104,6 +110,7 @@ export async function queryViaMetadata(ctx, args) {
104
110
  reviewStatus: 'none',
105
111
  planningOnly: true,
106
112
  contextPack,
113
+ datalexJoinGuidance,
107
114
  routeDecision: contextPack?.routeDecision,
108
115
  allowedSqlContext: contextPack?.allowedSqlContext,
109
116
  selectedRelations: contextPack?.retrievalDiagnostics.selectedRelations?.slice(0, 12) ?? [],
@@ -183,6 +190,7 @@ export async function queryViaMetadata(ctx, args) {
183
190
  trustStatus: metadataTrustStatus('draft_ready', intent, draftBlock, undefined, contextValidation.warnings),
184
191
  evidence: metadataEvidence(intent, args, { status: 'dry_run', draftBlock, validationWarnings: contextValidation.warnings }, contextPack),
185
192
  contextPack,
193
+ datalexJoinGuidance,
186
194
  reason: 'dryRun=true; SQL not executed. Returned proposal only.',
187
195
  proposedSql,
188
196
  draftBlock,
@@ -273,6 +281,7 @@ export async function queryViaMetadata(ctx, args) {
273
281
  validationWarnings: contextValidation.warnings,
274
282
  }, contextPack),
275
283
  contextPack,
284
+ datalexJoinGuidance,
276
285
  reason: 'no certified block matched the question; result derived from manifest + dbt schema',
277
286
  question: args.question,
278
287
  rowCount: rows.length,
@@ -287,6 +296,109 @@ export async function queryViaMetadata(ctx, args) {
287
296
  : undefined,
288
297
  };
289
298
  }
299
+ /**
300
+ * Build grain-safe join guidance from the DataLex manifest for the entities a
301
+ * Tier-2 question touches. This is what turns conformance + typed relationships
302
+ * into something the agent can act on BEFORE it writes SQL: the canonical key to
303
+ * join each business concept on, and — for each pair — whether joining one onto
304
+ * the other fans out (and therefore needs aggregation to stay grain-safe).
305
+ *
306
+ * `refs` are the tables/entities the agent thinks are involved; physical model
307
+ * names (`dim_customer`), physical entity names (`DimCustomer`), and concept
308
+ * names (`Customer`) all resolve. With none resolved we surface the whole
309
+ * (small) conformance map so the agent still sees the modeled joins.
310
+ *
311
+ * Returns undefined when no DataLex manifest is loaded — Tier-2 must keep working
312
+ * for projects that never adopted DataLex (graduated trust).
313
+ */
314
+ export function buildDataLexJoinGuidance(registry, refs) {
315
+ // Gate on conformance, NOT registry.isLoaded() — that's contracts-based, and a
316
+ // modeling-primary manifest carries conformance + relationships with zero
317
+ // contracts. No conformance (or no manifest) => no guidance; Tier-2 still runs.
318
+ const conformance = registry.conformance();
319
+ if (conformance.length === 0)
320
+ return undefined;
321
+ const conceptByKey = new Map();
322
+ for (const c of conformance) {
323
+ conceptByKey.set(c.concept.toLowerCase(), c);
324
+ for (const p of c.physical ?? []) {
325
+ if (p.entity)
326
+ conceptByKey.set(p.entity.toLowerCase(), c);
327
+ if (p.binding?.ref)
328
+ conceptByKey.set(p.binding.ref.toLowerCase(), c);
329
+ }
330
+ }
331
+ const involved = [];
332
+ const seen = new Set();
333
+ for (const ref of refs) {
334
+ const c = conceptByKey.get(String(ref ?? '').toLowerCase());
335
+ if (c && !seen.has(c.concept.toLowerCase())) {
336
+ seen.add(c.concept.toLowerCase());
337
+ involved.push(c);
338
+ }
339
+ }
340
+ // Scope to the involved concepts; with none resolved, fall back to the whole
341
+ // (bounded) conformance map so the agent still gets the modeled join keys.
342
+ const concepts = involved.length > 0 ? involved : conformance.slice(0, 12);
343
+ const entities = concepts.map((c) => ({
344
+ concept: c.concept,
345
+ domain: c.domain,
346
+ canonicalKey: c.canonical_key,
347
+ physical: (c.physical ?? [])
348
+ .map((p) => p.binding?.ref)
349
+ .filter((r) => Boolean(r)),
350
+ }));
351
+ const joins = [];
352
+ const unresolved = [];
353
+ for (let i = 0; i < concepts.length; i += 1) {
354
+ for (let j = i + 1; j < concepts.length; j += 1) {
355
+ const a = concepts[i];
356
+ const b = concepts[j];
357
+ let jp = registry.joinPath(a.concept, b.concept, {
358
+ baseDomain: a.domain,
359
+ targetDomain: b.domain,
360
+ });
361
+ // Prefer the non-fanning orientation (join the "one" side onto the "many"
362
+ // side, i.e. the dimension onto the fact) so the primary guidance is the
363
+ // grain-safe join; the fan-out direction is still called out in the text.
364
+ if (jp.ok && jp.fansOut) {
365
+ const flipped = registry.joinPath(b.concept, a.concept, {
366
+ baseDomain: b.domain,
367
+ targetDomain: a.domain,
368
+ });
369
+ if (flipped.ok && !flipped.fansOut)
370
+ jp = flipped;
371
+ }
372
+ if (!jp.ok) {
373
+ if (jp.reason === 'ambiguous') {
374
+ unresolved.push({ from: a.concept, to: b.concept, reason: jp.reason, message: jp.message });
375
+ }
376
+ // 'no_relationship' is expected for unrelated pairs — skip quietly.
377
+ continue;
378
+ }
379
+ const on = jp.base.column && jp.target.column
380
+ ? `${jp.base.entity}.${jp.base.column} = ${jp.target.entity}.${jp.target.column}`
381
+ : undefined;
382
+ joins.push({
383
+ from: jp.base.entity,
384
+ to: jp.target.entity,
385
+ on,
386
+ cardinality: jp.cardinality,
387
+ fansOut: jp.fansOut,
388
+ guidance: jp.fansOut
389
+ ? `${jp.base.entity} ↔ ${jp.target.entity} is ${jp.cardinality}; either direction can multiply rows — aggregate to one grain (GROUP BY the canonical key) before joining.`
390
+ : `Each ${jp.base.entity} has one ${jp.target.entity}: join ${jp.target.entity} onto ${jp.base.entity}${on ? ` on ${on}` : ''} without fan-out. Aggregating ${jp.base.entity} per ${jp.target.entity} fans out — GROUP BY the ${jp.target.entity} key.`,
391
+ });
392
+ }
393
+ }
394
+ return {
395
+ source: 'datalex_manifest',
396
+ note: 'Modeled join semantics from DataLex. Join each concept on its canonical key and respect fan-out to keep results grain-safe.',
397
+ entities,
398
+ joins: joins.slice(0, 25),
399
+ ...(unresolved.length > 0 ? { unresolved } : {}),
400
+ };
401
+ }
290
402
  function normalizeMetadataResearchIntent(value, question) {
291
403
  if (value === 'diagnose_change'
292
404
  || value === 'driver_breakdown'