@databrainhq/mcp-server 0.3.0-rc.6 → 0.3.0-rc.8

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.
@@ -18,7 +18,7 @@ import {
18
18
  toolError,
19
19
  toolSuccess,
20
20
  validateBundle
21
- } from "./chunk-SKU37UVJ.js";
21
+ } from "./chunk-RDMVO56D.js";
22
22
 
23
23
  // src/apps/contract.ts
24
24
  import { z } from "zod";
@@ -178,6 +178,12 @@ var TOOL_METADATA = {
178
178
  preview_chart: { category: "internal_analytics", workflowIntent: "query_data", effect: "view" },
179
179
  save_chart_as_dashboard: { category: "internal_analytics", workflowIntent: "author_dashboard", effect: "commit" },
180
180
  // Semantic layer
181
+ search_semantic_model: { category: "semantic_layer", workflowIntent: "query_data", effect: "read" },
182
+ get_semantic_entity: { category: "semantic_layer", workflowIntent: "query_data", effect: "read" },
183
+ propose_semantic_change: { category: "semantic_layer", workflowIntent: "model_data", effect: "read" },
184
+ apply_semantic_change: { category: "semantic_layer", workflowIntent: "model_data", effect: "commit" },
185
+ eval_semantic_model: { category: "semantic_layer", workflowIntent: "query_data", effect: "read" },
186
+ semantic_readiness: { category: "semantic_layer", workflowIntent: "model_data", effect: "read" },
181
187
  get_semantic_layer: { category: "semantic_layer", workflowIntent: "query_data", effect: "read" },
182
188
  update_semantic_layer: { category: "semantic_layer", workflowIntent: "query_data", effect: "commit" },
183
189
  start_semantic_layer_generation: { category: "semantic_layer", workflowIntent: "query_data", effect: "commit" },
@@ -492,18 +498,18 @@ var QueryApi = class {
492
498
  throw err;
493
499
  }
494
500
  }
495
- async startSemanticLayerGeneration(datamartId) {
501
+ async startSemanticLayerGeneration(datamartId2) {
496
502
  return this.client.request({
497
503
  method: "POST",
498
- path: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId)}/semantic-layer/generate`,
504
+ path: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId2)}/semantic-layer/generate`,
499
505
  auth: "service",
500
506
  invalidateCache: ["semantic_layer_"]
501
507
  });
502
508
  }
503
- async getSemanticLayerGenerationStatus(datamartId) {
509
+ async getSemanticLayerGenerationStatus(datamartId2) {
504
510
  return this.client.request({
505
511
  method: "GET",
506
- path: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId)}/semantic-layer/generation-status`,
512
+ path: `/api/v2/data-app/datamarts/${encodeURIComponent(datamartId2)}/semantic-layer/generation-status`,
507
513
  auth: "service"
508
514
  });
509
515
  }
@@ -526,9 +532,9 @@ function normalizeRecords(value) {
526
532
  record.items,
527
533
  record.data
528
534
  ];
529
- for (const candidate of candidates) {
530
- if (Array.isArray(candidate)) {
531
- return candidate.filter((item) => asRecord(item) !== null);
535
+ for (const candidate2 of candidates) {
536
+ if (Array.isArray(candidate2)) {
537
+ return candidate2.filter((item) => asRecord(item) !== null);
532
538
  }
533
539
  }
534
540
  return [];
@@ -557,6 +563,65 @@ var ScheduledReportsApi = class {
557
563
  }
558
564
  };
559
565
 
566
+ // src/api/semantic-model-api.ts
567
+ var SemanticModelApi = class {
568
+ constructor(client) {
569
+ this.client = client;
570
+ }
571
+ path(id2, verb) {
572
+ return `/api/v2/semantic/${encodeURIComponent(id2)}/${verb}`;
573
+ }
574
+ pull(id2) {
575
+ return this.client.request({ method: "GET", path: this.path(id2, "pull"), auth: "service" });
576
+ }
577
+ retrieve(id2, q, k) {
578
+ return this.client.request({
579
+ method: "GET",
580
+ path: this.path(id2, "retrieve"),
581
+ auth: "service",
582
+ query: { q, k: String(k) }
583
+ });
584
+ }
585
+ validate(id2, source2) {
586
+ return this.client.request({
587
+ method: "POST",
588
+ path: this.path(id2, "validate"),
589
+ auth: "service",
590
+ body: { source: source2 }
591
+ });
592
+ }
593
+ plan(id2, source2, expected_content_hash) {
594
+ return this.client.request({
595
+ method: "POST",
596
+ path: this.path(id2, "plan"),
597
+ auth: "service",
598
+ body: { source: source2, expected_content_hash }
599
+ });
600
+ }
601
+ apply(id2, source2, expected_content_hash) {
602
+ return this.client.request({
603
+ method: "POST",
604
+ path: this.path(id2, "apply"),
605
+ auth: "service",
606
+ body: { source: source2, expected_content_hash },
607
+ noRetryOnTimeout: true
608
+ });
609
+ }
610
+ evaluate(id2, body) {
611
+ return this.client.request({
612
+ method: "POST",
613
+ path: this.path(id2, "eval"),
614
+ auth: "service",
615
+ body,
616
+ timeoutMs: 3e5,
617
+ noRetryOnTimeout: true
618
+ });
619
+ }
620
+ readiness(id2) {
621
+ return this.client.request({ method: "GET", path: this.path(id2, "readiness"), auth: "service" });
622
+ }
623
+ };
624
+
560
625
  // src/api/tenants-api.ts
561
626
  var TenantsApi = class {
562
627
  constructor(client) {
@@ -739,8 +804,8 @@ function extractAccessSettings(embed) {
739
804
  asRecord2(metadataConfig?.accessSettings),
740
805
  asRecord2(options?.accessSettings)
741
806
  ].filter((c) => !!c);
742
- for (const candidate of candidates) {
743
- if (Object.keys(candidate).length > 0) return candidate;
807
+ for (const candidate2 of candidates) {
808
+ if (Object.keys(candidate2).length > 0) return candidate2;
744
809
  }
745
810
  return null;
746
811
  }
@@ -1472,6 +1537,7 @@ function createServiceBundle(client) {
1472
1537
  const guestTokenApi = new GuestTokenApi(client);
1473
1538
  const queryApi = new QueryApi(client);
1474
1539
  const scheduledReportsApi = new ScheduledReportsApi(client);
1540
+ const semanticModelApi = new SemanticModelApi(client);
1475
1541
  const customChartsApi = new CustomChartsApi(client);
1476
1542
  const tenantsApi = new TenantsApi(client);
1477
1543
  const widgetsApi = new WidgetsApi(client);
@@ -1487,6 +1553,7 @@ function createServiceBundle(client) {
1487
1553
  workspacesApi,
1488
1554
  queryApi,
1489
1555
  scheduledReportsApi,
1556
+ semanticModelApi,
1490
1557
  customChartsApi,
1491
1558
  tenantsApi,
1492
1559
  widgetsApi,
@@ -5316,16 +5383,403 @@ ${result.feedback}`);
5316
5383
  );
5317
5384
  }
5318
5385
 
5319
- // src/tools/query/update-semantic-layer.ts
5386
+ // src/tools/query/semantic-model.ts
5320
5387
  import { z as z27 } from "zod";
5321
- var columnSchema2 = z27.object({
5322
- name: z27.string().describe("Column name (must exist in the datamart)"),
5323
- description: z27.string().optional().describe("Business-friendly description of what this column represents"),
5324
- synonyms: z27.array(z27.string()).optional().describe(
5388
+
5389
+ // src/services/semantic-source.ts
5390
+ import { parseDocument, stringify as stringify2 } from "yaml";
5391
+ var ENTITY_COLLECTIONS = {
5392
+ table: ["tables"],
5393
+ column: ["columns"],
5394
+ measure: ["measures"],
5395
+ metric: ["metrics"],
5396
+ join: ["joins"],
5397
+ entity: ["entities"],
5398
+ glossary: ["glossary"],
5399
+ saved_query: ["saved_queries", "verified_queries"],
5400
+ instruction: ["instructions", "agent_instructions"],
5401
+ access_rule: ["access", "policies"]
5402
+ };
5403
+ function isObject(value) {
5404
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5405
+ }
5406
+ function assertSafe(value, seen = /* @__PURE__ */ new Set(), depth = 0) {
5407
+ if (depth > 100) throw new Error("Semantic source nesting exceeds 100 levels.");
5408
+ if (!value || typeof value !== "object") return;
5409
+ if (seen.has(value)) throw new Error("Recursive YAML aliases are not supported.");
5410
+ seen.add(value);
5411
+ for (const [key, child] of Object.entries(value)) {
5412
+ if (["__proto__", "constructor", "prototype"].includes(key)) throw new Error(`Unsupported source key: ${key}.`);
5413
+ assertSafe(child, seen, depth + 1);
5414
+ }
5415
+ seen.delete(value);
5416
+ }
5417
+ function parseSemanticSource(source2) {
5418
+ const document = parseDocument(source2, { prettyErrors: false, uniqueKeys: true });
5419
+ if (document.errors.length || document.warnings.length) {
5420
+ throw new Error((document.errors[0] ?? document.warnings[0]).message);
5421
+ }
5422
+ const value = document.toJS({ maxAliasCount: 100 });
5423
+ if (!isObject(value)) throw new Error("Semantic source must be a YAML mapping.");
5424
+ assertSafe(value);
5425
+ return value;
5426
+ }
5427
+ function semanticEntities(source2, visibleOnly = false) {
5428
+ const out = [];
5429
+ for (const [kind, keys] of Object.entries(ENTITY_COLLECTIONS)) {
5430
+ if (kind === "column") continue;
5431
+ const key = keys.find((key2) => Array.isArray(source2[key2]));
5432
+ const entries = key ? source2[key] : [];
5433
+ for (const entry of entries) {
5434
+ if (!isObject(entry) || typeof entry.name !== "string" || visibleOnly && entry.hidden === true) continue;
5435
+ const columns = Array.isArray(entry.columns) ? entry.columns.filter(isObject) : [];
5436
+ const entity = visibleOnly && kind === "table" ? { ...entry, columns: columns.filter((column) => column.hidden !== true) } : entry;
5437
+ out.push({ id: `${kind}:${entry.name}`, kind, name: entry.name, entity });
5438
+ if (kind === "table") for (const column of columns) {
5439
+ if (typeof column.name !== "string" || visibleOnly && column.hidden === true) continue;
5440
+ const name = `${entry.name}.${column.name}`;
5441
+ out.push({ id: `column:${name}`, kind: "column", name, entity: column });
5442
+ }
5443
+ }
5444
+ }
5445
+ return out;
5446
+ }
5447
+ function mergePatch(base, patch) {
5448
+ if (isObject(patch)) {
5449
+ const next = isObject(base) ? { ...base } : {};
5450
+ for (const [key, value] of Object.entries(patch)) {
5451
+ if (value === null) delete next[key];
5452
+ else next[key] = mergePatch(next[key], value);
5453
+ }
5454
+ return next;
5455
+ }
5456
+ if (Array.isArray(patch) && patch.length && patch.every((item) => isObject(item) && typeof item.name === "string")) {
5457
+ const next = Array.isArray(base) ? [...base] : [];
5458
+ const names = /* @__PURE__ */ new Set();
5459
+ for (const item of patch) {
5460
+ const name = item.name;
5461
+ if (names.has(name)) throw new Error(`Duplicate patch entity: ${name}.`);
5462
+ names.add(name);
5463
+ const index = next.findIndex((entry) => isObject(entry) && entry.name === name);
5464
+ if (index < 0) next.push(mergePatch({}, item));
5465
+ else next[index] = mergePatch(next[index], item);
5466
+ }
5467
+ return next;
5468
+ }
5469
+ return patch;
5470
+ }
5471
+ function changeSemanticSource(source2, input) {
5472
+ const model = parseSemanticSource(source2);
5473
+ if (input.yaml_patch !== void 0) return stringify2(mergePatch(model, parseSemanticSource(input.yaml_patch)));
5474
+ const change = input.change;
5475
+ assertSafe(change);
5476
+ let parent = model;
5477
+ if (change.kind === "column") {
5478
+ if (!change.table) throw new Error("A column change requires table and an unqualified column name.");
5479
+ const table = (Array.isArray(model.tables) ? model.tables : []).find(
5480
+ (item) => isObject(item) && item.name === change.table
5481
+ );
5482
+ if (!isObject(table)) throw new Error(`Table not found: ${change.table}.`);
5483
+ parent = table;
5484
+ } else if (change.table) throw new Error("table is only valid for a column change.");
5485
+ const keys = ENTITY_COLLECTIONS[change.kind];
5486
+ const key = keys.find((key2) => Array.isArray(parent[key2])) ?? keys[0];
5487
+ const entries = Array.isArray(parent[key]) ? [...parent[key]] : [];
5488
+ const index = entries.findIndex((entry) => isObject(entry) && entry.name === change.name);
5489
+ if (change.operation === "remove") {
5490
+ if (change.value !== void 0) throw new Error("A remove change must not include value.");
5491
+ if (index < 0) throw new Error(`Entity not found: ${change.kind} ${change.name}.`);
5492
+ entries.splice(index, 1);
5493
+ } else {
5494
+ if (!change.value) throw new Error("An upsert change requires value.");
5495
+ if (change.value.name !== void 0 && change.value.name !== change.name) throw new Error("value.name must match name.");
5496
+ const value = mergePatch(index < 0 ? {} : entries[index], { ...change.value, name: change.name });
5497
+ if (index < 0) entries.push(value);
5498
+ else entries[index] = value;
5499
+ }
5500
+ parent[key] = entries;
5501
+ return stringify2(model);
5502
+ }
5503
+
5504
+ // src/services/semantic-retrieval.ts
5505
+ function tokens(text2) {
5506
+ return text2.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean);
5507
+ }
5508
+ function text(value) {
5509
+ return typeof value === "string" ? value : Array.isArray(value) ? value.map(text).join(" ") : "";
5510
+ }
5511
+ function retrieveSemanticSource(source2, question, k) {
5512
+ const nodes = semanticEntities(parseSemanticSource(source2), true);
5513
+ const query = new Set(tokens(question));
5514
+ const score = (node) => {
5515
+ const fields = [
5516
+ [node.name, 8],
5517
+ [node.entity.alias ?? node.entity.term, 6],
5518
+ [node.entity.synonyms, 4],
5519
+ [node.entity.description ?? node.entity.definition ?? node.entity.question, 2]
5520
+ ];
5521
+ return fields.reduce((sum, [value, weight]) => {
5522
+ const words = new Set(tokens(text(value)));
5523
+ return sum + [...query].filter((word) => words.has(word)).length * weight;
5524
+ }, 0);
5525
+ };
5526
+ const ranked = nodes.map((node) => ({ ...node, score: score(node), match: "lexical" })).filter((node) => node.score > 0).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id)).slice(0, k);
5527
+ const edges = new Map(nodes.map((node) => [node.id, /* @__PURE__ */ new Set()]));
5528
+ const byName = /* @__PURE__ */ new Map();
5529
+ for (const node of nodes) byName.set(node.name, [...byName.get(node.name) ?? [], node.id]);
5530
+ const link = (a, b) => {
5531
+ if (a === b || !edges.has(b)) return;
5532
+ edges.get(a).add(b);
5533
+ edges.get(b).add(a);
5534
+ };
5535
+ const refKeys = /* @__PURE__ */ new Set([
5536
+ "table",
5537
+ "column",
5538
+ "columns",
5539
+ "references",
5540
+ "measure",
5541
+ "numerator",
5542
+ "denominator",
5543
+ "metrics",
5544
+ "measures",
5545
+ "glossary_refs",
5546
+ "left",
5547
+ "right",
5548
+ "from",
5549
+ "to",
5550
+ "on",
5551
+ "bindings",
5552
+ "scope",
5553
+ "targets",
5554
+ "tables",
5555
+ "grain",
5556
+ "identifiers",
5557
+ "default_time_dimension",
5558
+ "expression",
5559
+ "sql",
5560
+ "on_sql",
5561
+ "predicate",
5562
+ "type_config"
5563
+ ]);
5564
+ const refs = (value) => {
5565
+ if (typeof value === "string") return value.match(/[\p{L}_][\p{L}\p{N}_]*(?:\.[\p{L}_][\p{L}\p{N}_]*)?/gu) ?? [];
5566
+ if (Array.isArray(value)) return value.flatMap(refs);
5567
+ if (isObject(value)) return Object.entries(value).flatMap(([key, child]) => refKeys.has(key) ? refs(child) : []);
5568
+ return [];
5569
+ };
5570
+ for (const node of nodes) {
5571
+ if (node.kind === "column") link(node.id, `table:${node.name.split(".")[0]}`);
5572
+ for (const ref of refs(node.entity)) for (const id2 of byName.get(ref) ?? []) link(node.id, id2);
5573
+ }
5574
+ const chosen = new Map(ranked.map((node) => [node.id, node]));
5575
+ for (const node of ranked) {
5576
+ for (const id2 of [...edges.get(node.id)].sort()) {
5577
+ if (!chosen.has(id2)) {
5578
+ const adjacent = nodes.find((node2) => node2.id === id2);
5579
+ chosen.set(id2, { ...adjacent, score: 0, match: "adjacency" });
5580
+ }
5581
+ }
5582
+ }
5583
+ return { results: [...chosen.values()], k, lexical_matches: ranked.length, retrieval: "local_lexical" };
5584
+ }
5585
+
5586
+ // src/tools/query/semantic-model.ts
5587
+ var datamartId = z27.string().trim().min(1).max(256).describe("Datamart ID from list_datamarts.");
5588
+ var source = z27.string().min(1).max(2e6).describe("Complete proposed semantic model as YAML.");
5589
+ var entityKind = z27.enum(["table", "column", "measure", "metric", "join", "entity", "glossary", "saved_query", "instruction", "access_rule"]);
5590
+ var changeInputs = {
5591
+ source: source.optional(),
5592
+ yaml_patch: z27.string().min(1).max(2e6).optional().describe("YAML mapping merged into the pulled model. Named lists merge by name. Other lists replace. Null removes a field. An empty list clears it."),
5593
+ change: z27.object({
5594
+ operation: z27.enum(["upsert", "remove"]),
5595
+ kind: entityKind,
5596
+ name: z27.string().trim().min(1),
5597
+ table: z27.string().trim().min(1).optional().describe("Required for columns. Use the table name and an unqualified column name."),
5598
+ value: z27.record(z27.unknown()).optional().describe("Fields to merge for upsert. Omit for remove.")
5599
+ }).strict().optional().describe("One named entity change. Supply exactly one of source, yaml_patch or change.")
5600
+ };
5601
+ var expectedHash = z27.string().trim().min(1).nullable().optional().describe("Stored content_hash from the reviewed proposal. Null explicitly means the first apply. Required when dry_run is false.");
5602
+ function errorCode(error) {
5603
+ const nested = isObject(error.details) && isObject(error.details.error) ? error.details.error : void 0;
5604
+ return typeof nested?.code === "string" ? nested.code : error.code;
5605
+ }
5606
+ function failure(error) {
5607
+ if (error instanceof DatabrainApiError) {
5608
+ if (error.status === 409 || error.status === 422) {
5609
+ return toolError(stringify({
5610
+ status: error.status,
5611
+ code: errorCode(error),
5612
+ reason: error.message,
5613
+ details: error.details,
5614
+ recovery: error.status === 409 ? "Pull the current model and review a new proposal before applying." : "Correct the validation errors and propose the change again."
5615
+ }));
5616
+ }
5617
+ return error.toToolResponse();
5618
+ }
5619
+ return toolError(`Semantic model error: ${error instanceof Error ? error.message : String(error)}`);
5620
+ }
5621
+ async function candidate(api, id2, input) {
5622
+ if ([input.source, input.yaml_patch, input.change].filter((value) => value !== void 0).length !== 1) {
5623
+ throw new Error("Supply exactly one of source, yaml_patch or change.");
5624
+ }
5625
+ let stored;
5626
+ try {
5627
+ stored = await api.pull(id2);
5628
+ } catch (error) {
5629
+ if (!(error instanceof DatabrainApiError && error.status === 404 && errorCode(error) === "layer_not_found" && input.source !== void 0)) throw error;
5630
+ }
5631
+ const hash = stored?.content_hash ?? null;
5632
+ if (input.expected_content_hash !== void 0 && input.expected_content_hash !== hash) {
5633
+ throw new DatabrainApiError(409, "content_hash_mismatch", "The model changed since the proposal was reviewed.", { current_hash: hash });
5634
+ }
5635
+ return {
5636
+ source: input.source ?? changeSemanticSource(stored.source, input),
5637
+ expected_content_hash: input.expected_content_hash === void 0 ? hash : input.expected_content_hash
5638
+ };
5639
+ }
5640
+ async function proposal(api, id2, input) {
5641
+ const next = await candidate(api, id2, input);
5642
+ const validation = await api.validate(id2, next.source);
5643
+ const plan = validation.ok ? await api.plan(id2, next.source, next.expected_content_hash) : null;
5644
+ return { ...next, validation, errors: validation.errors, plan, dry_run: true };
5645
+ }
5646
+ function registerSearchSemanticModel(server, client) {
5647
+ const api = createServiceBundle(client).semanticModelApi;
5648
+ server.tool(
5649
+ "search_semantic_model",
5650
+ "Find semantic model entities relevant to a question. Ranks names, aliases, synonyms and descriptions, then includes related entities. Uses the stored model and never writes.",
5651
+ { datamartId, question: z27.string().trim().min(1).max(1e4), k: z27.number().int().min(1).max(100).default(10) },
5652
+ READ_ONLY,
5653
+ async ({ datamartId: datamartId2, question, k }) => {
5654
+ try {
5655
+ try {
5656
+ return toolSuccess(stringify(await api.retrieve(datamartId2, question, k)));
5657
+ } catch (error) {
5658
+ if (!(error instanceof DatabrainApiError && [404, 405, 501].includes(error.status))) throw error;
5659
+ if (["datamart_not_found", "layer_not_found", "forbidden", "unauthorized"].includes(errorCode(error))) throw error;
5660
+ }
5661
+ const stored = await api.pull(datamartId2);
5662
+ return toolSuccess(stringify({ ...retrieveSemanticSource(stored.source, question, k), content_hash: stored.content_hash }));
5663
+ } catch (error) {
5664
+ return failure(error);
5665
+ }
5666
+ }
5667
+ );
5668
+ }
5669
+ function registerGetSemanticEntity(server, client) {
5670
+ const api = createServiceBundle(client).semanticModelApi;
5671
+ server.tool(
5672
+ "get_semantic_entity",
5673
+ "Read one entity by kind and exact name from the stored semantic model. For a column, use table.column. Returns the stored content hash for change review.",
5674
+ { datamartId, kind: entityKind, name: z27.string().trim().min(1) },
5675
+ READ_ONLY,
5676
+ async ({ datamartId: datamartId2, kind, name }) => {
5677
+ try {
5678
+ const stored = await api.pull(datamartId2);
5679
+ const found = semanticEntities(parseSemanticSource(stored.source)).filter((entity) => entity.kind === kind && entity.name === name);
5680
+ if (found.length !== 1) return toolError(`Expected one ${kind} named ${name}; found ${found.length}. Use search_semantic_model to find its exact name.`);
5681
+ return toolSuccess(stringify({ ...found[0], content_hash: stored.content_hash }));
5682
+ } catch (error) {
5683
+ return failure(error);
5684
+ }
5685
+ }
5686
+ );
5687
+ }
5688
+ function registerProposeSemanticChange(server, client) {
5689
+ const api = createServiceBundle(client).semanticModelApi;
5690
+ server.tool(
5691
+ "propose_semantic_change",
5692
+ "Prepare a semantic model change from complete YAML, a YAML patch or one structured change. Returns validation errors, the server plan, candidate YAML and the stored hash. Never writes.",
5693
+ { datamartId, ...changeInputs },
5694
+ READ_ONLY,
5695
+ async (params) => {
5696
+ try {
5697
+ return toolSuccess(stringify(await proposal(api, params.datamartId, params)));
5698
+ } catch (error) {
5699
+ return failure(error);
5700
+ }
5701
+ }
5702
+ );
5703
+ }
5704
+ function registerApplySemanticChange(server, client) {
5705
+ const api = createServiceBundle(client).semanticModelApi;
5706
+ server.tool(
5707
+ "apply_semantic_change",
5708
+ "Preview a semantic change by default. Setting dry_run to false applies it live and can remove entities. Requires the reviewed content hash and confirm set to APPLY_TO_PRODUCTION supplied by the user.",
5709
+ {
5710
+ datamartId,
5711
+ ...changeInputs,
5712
+ dry_run: z27.boolean().default(true),
5713
+ expected_content_hash: expectedHash,
5714
+ confirm: z27.string().optional().describe("User supplied APPLY_TO_PRODUCTION after reviewing the plan. Never invent confirmation.")
5715
+ },
5716
+ DESTRUCTIVE,
5717
+ async (params) => {
5718
+ try {
5719
+ if (params.dry_run !== false) return toolSuccess(stringify(await proposal(api, params.datamartId, params)));
5720
+ const refusal = validateProductionApply(params);
5721
+ if (refusal) return toolError(refusal);
5722
+ if (params.expected_content_hash === void 0) return toolError("expected_content_hash is required. Use the stored hash from the reviewed proposal, or null for the first apply.");
5723
+ const next = await candidate(api, params.datamartId, params);
5724
+ const validation = await api.validate(params.datamartId, next.source);
5725
+ if (!validation.ok) return failure(new DatabrainApiError(422, "validation_failed", "Apply refused because the model does not validate.", { validation }));
5726
+ return toolSuccess(stringify(await api.apply(params.datamartId, next.source, next.expected_content_hash)));
5727
+ } catch (error) {
5728
+ return failure(error);
5729
+ }
5730
+ }
5731
+ );
5732
+ }
5733
+ function registerEvalSemanticModel(server, client) {
5734
+ const api = createServiceBundle(client).semanticModelApi;
5735
+ server.tool(
5736
+ "eval_semantic_model",
5737
+ "Evaluate saved queries and optional questions against a proposed or stored semantic model. Returns the pass rate and each verdict with its reason. Runs warehouse queries without changing the model.",
5738
+ { datamartId, source: source.optional(), questions: z27.array(z27.union([z27.string().trim().min(1), z27.object({
5739
+ name: z27.string().optional(),
5740
+ question: z27.string().min(1),
5741
+ sql: z27.string().optional(),
5742
+ expected_columns: z27.array(z27.string()).optional(),
5743
+ expected_empty: z27.boolean().optional()
5744
+ }).strict()])).max(100).optional() },
5745
+ READ_ONLY,
5746
+ async ({ datamartId: datamartId2, source: source2, questions }) => {
5747
+ const shaped = questions?.map((entry) => typeof entry === "string" ? { question: entry } : entry);
5748
+ try {
5749
+ return toolSuccess(stringify(await api.evaluate(datamartId2, { source: source2, questions: shaped })));
5750
+ } catch (error) {
5751
+ return failure(error);
5752
+ }
5753
+ }
5754
+ );
5755
+ }
5756
+ function registerSemanticReadiness(server, client) {
5757
+ const api = createServiceBundle(client).semanticModelApi;
5758
+ server.tool(
5759
+ "semantic_readiness",
5760
+ "Read the semantic readiness number, verdict and open checks for a stored model. Uses the server result without changing the model.",
5761
+ { datamartId },
5762
+ READ_ONLY,
5763
+ async ({ datamartId: datamartId2 }) => {
5764
+ try {
5765
+ return toolSuccess(stringify(await api.readiness(datamartId2)));
5766
+ } catch (error) {
5767
+ return failure(error);
5768
+ }
5769
+ }
5770
+ );
5771
+ }
5772
+
5773
+ // src/tools/query/update-semantic-layer.ts
5774
+ import { z as z28 } from "zod";
5775
+ var columnSchema2 = z28.object({
5776
+ name: z28.string().describe("Column name (must exist in the datamart)"),
5777
+ description: z28.string().optional().describe("Business-friendly description of what this column represents"),
5778
+ synonyms: z28.array(z28.string()).optional().describe(
5325
5779
  'Alternative names users might use for this column (e.g. ["revenue", "sales amount"])'
5326
5780
  ),
5327
- miscellaneousInfo: z27.string().optional().describe("Additional context such as valid values, units, or caveats"),
5328
- columnType: z27.enum([
5781
+ miscellaneousInfo: z28.string().optional().describe("Additional context such as valid values, units, or caveats"),
5782
+ columnType: z28.enum([
5329
5783
  "String",
5330
5784
  "Long String",
5331
5785
  "String (Custom)",
@@ -5340,21 +5794,21 @@ var columnSchema2 = z27.object({
5340
5794
  // The public PUT accepts these three as well (docs: Update Semantic Layer).
5341
5795
  // They were missing here, and zod strips unknown keys, so a caller who set
5342
5796
  // `isIdentifier: true` got a success message and a row with `false`.
5343
- columnTypeConfig: z27.union([z27.record(z27.string(), z27.string()), z27.string(), z27.object({ lowerLimit: z27.number(), upperLimit: z27.number() }), z27.null()]).optional().describe("Shape depends on columnType: value-to-description object for String / String (Custom) / ENUM / Mapper, { lowerLimit, upperLimit } for Range, a string for Expression and JSON. Omit for Identifier, Number and Long String."),
5344
- isIdentifier: z27.boolean().optional().describe("Mark the column as an identifier (primary or foreign key)."),
5345
- isNotIndexed: z27.boolean().optional().describe("Exclude the column from AI indexing.")
5797
+ columnTypeConfig: z28.union([z28.record(z28.string(), z28.string()), z28.string(), z28.object({ lowerLimit: z28.number(), upperLimit: z28.number() }), z28.null()]).optional().describe("Shape depends on columnType: value-to-description object for String / String (Custom) / ENUM / Mapper, { lowerLimit, upperLimit } for Range, a string for Expression and JSON. Omit for Identifier, Number and Long String."),
5798
+ isIdentifier: z28.boolean().optional().describe("Mark the column as an identifier (primary or foreign key)."),
5799
+ isNotIndexed: z28.boolean().optional().describe("Exclude the column from AI indexing.")
5346
5800
  });
5347
- var tableSchema2 = z27.object({
5348
- name: z27.string().describe("Table name (must exist in the datamart)"),
5349
- schemaName: z27.string().optional().describe("Schema the table lives in, when the datamart has the same table name in more than one schema."),
5350
- description: z27.string().optional().describe(
5801
+ var tableSchema2 = z28.object({
5802
+ name: z28.string().describe("Table name (must exist in the datamart)"),
5803
+ schemaName: z28.string().optional().describe("Schema the table lives in, when the datamart has the same table name in more than one schema."),
5804
+ description: z28.string().optional().describe(
5351
5805
  "Business-friendly description of what this table represents"
5352
5806
  ),
5353
- synonyms: z27.array(z27.string()).optional().describe("Alternative names for this table"),
5354
- miscellaneousInfo: z27.string().optional().describe(
5807
+ synonyms: z28.array(z28.string()).optional().describe("Alternative names for this table"),
5808
+ miscellaneousInfo: z28.string().optional().describe(
5355
5809
  "Additional context such as how the table relates to other tables"
5356
5810
  ),
5357
- columns: z27.array(columnSchema2).optional().describe("Column-level semantic definitions to update")
5811
+ columns: z28.array(columnSchema2).optional().describe("Column-level semantic definitions to update")
5358
5812
  });
5359
5813
  function columnKey(tableName, columnName) {
5360
5814
  return `${tableName.toLowerCase()}\0${columnName.toLowerCase()}`;
@@ -5431,13 +5885,13 @@ function registerUpdateSemanticLayer(server, client) {
5431
5885
  "update_semantic_layer",
5432
5886
  "Populate or update the semantic layer for a datamart. Add table/column descriptions, synonyms, context. Required before ask_question works. Auto-generate descriptions from column names when empty.",
5433
5887
  {
5434
- datamartName: z27.string().optional().describe(
5888
+ datamartName: z28.string().optional().describe(
5435
5889
  "Name of the datamart to update. Omit to list available datamarts."
5436
5890
  ),
5437
- tables: z27.array(tableSchema2).optional().describe(
5891
+ tables: z28.array(tableSchema2).optional().describe(
5438
5892
  "Table and column definitions to update. Only include tables/columns you want to change."
5439
5893
  ),
5440
- feedback: z27.string().optional().describe(
5894
+ feedback: z28.string().optional().describe(
5441
5895
  'General feedback or context about the datamart (e.g. "This datamart contains e-commerce data for the US market").'
5442
5896
  )
5443
5897
  },
@@ -5499,7 +5953,7 @@ You can now:
5499
5953
  }
5500
5954
 
5501
5955
  // src/tools/query/semantic-generation.ts
5502
- import { z as z28 } from "zod";
5956
+ import { z as z29 } from "zod";
5503
5957
  function nonEmptyString(value) {
5504
5958
  return typeof value === "string" && value.trim().length > 0 ? value : null;
5505
5959
  }
@@ -5531,9 +5985,9 @@ async function resolveDatamartFromWorkspace(params) {
5531
5985
  });
5532
5986
  if (resolution.kind !== "resolved") return { ok: false, response: resolution.response };
5533
5987
  const selected = resolution.selected;
5534
- const datamartId = workspaceDatamartIdFrom(selected);
5988
+ const datamartId2 = workspaceDatamartIdFrom(selected);
5535
5989
  const workspaceDatamartName = workspaceDatamartNameFrom(selected);
5536
- if (!datamartId) {
5990
+ if (!datamartId2) {
5537
5991
  return {
5538
5992
  ok: false,
5539
5993
  response: toolError(
@@ -5551,8 +6005,8 @@ async function resolveDatamartFromWorkspace(params) {
5551
6005
  }
5552
6006
  return {
5553
6007
  ok: true,
5554
- datamartName: workspaceDatamartName || params.datamartName || datamartId,
5555
- datamartId,
6008
+ datamartName: workspaceDatamartName || params.datamartName || datamartId2,
6009
+ datamartId: datamartId2,
5556
6010
  workspaceName: resolution.name
5557
6011
  };
5558
6012
  }
@@ -5584,8 +6038,8 @@ async function resolveDatamart(params) {
5584
6038
  toolName: params.toolName
5585
6039
  });
5586
6040
  if (resolution.kind !== "resolved") return { ok: false, response: resolution.response };
5587
- const datamartId = datamartIdFrom(resolution.selected);
5588
- if (!datamartId) {
6041
+ const datamartId2 = datamartIdFrom(resolution.selected);
6042
+ if (!datamartId2) {
5589
6043
  return {
5590
6044
  ok: false,
5591
6045
  response: toolError(
@@ -5596,7 +6050,7 @@ async function resolveDatamart(params) {
5596
6050
  return {
5597
6051
  ok: true,
5598
6052
  datamartName: resolution.name,
5599
- datamartId
6053
+ datamartId: datamartId2
5600
6054
  };
5601
6055
  }
5602
6056
  function registerStartSemanticLayerGeneration(server, client) {
@@ -5605,9 +6059,9 @@ function registerStartSemanticLayerGeneration(server, client) {
5605
6059
  "start_semantic_layer_generation",
5606
6060
  "Start backend semantic-layer auto-generation for a datamart. Returns an async job id to poll.",
5607
6061
  {
5608
- datamartName: z28.string().optional().describe("Datamart name. Omit to list available datamarts"),
5609
- workspaceName: z28.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
5610
- datamartId: z28.string().optional().describe("Datamart UUID. Use only when already known.")
6062
+ datamartName: z29.string().optional().describe("Datamart name. Omit to list available datamarts"),
6063
+ workspaceName: z29.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
6064
+ datamartId: z29.string().optional().describe("Datamart UUID. Use only when already known.")
5611
6065
  },
5612
6066
  WRITE_CREATE,
5613
6067
  async (params) => {
@@ -5649,9 +6103,9 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
5649
6103
  "get_semantic_layer_generation_status",
5650
6104
  "Get semantic-layer auto-generation job status for a datamart.",
5651
6105
  {
5652
- datamartName: z28.string().optional().describe("Datamart name. Omit to list available datamarts"),
5653
- workspaceName: z28.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
5654
- datamartId: z28.string().optional().describe("Datamart UUID. Use only when already known.")
6106
+ datamartName: z29.string().optional().describe("Datamart name. Omit to list available datamarts"),
6107
+ workspaceName: z29.string().optional().describe("Workspace linked to the datamart. Use when datamartName resolution needs a dataMartId bridge."),
6108
+ datamartId: z29.string().optional().describe("Datamart UUID. Use only when already known.")
5655
6109
  },
5656
6110
  READ_ONLY,
5657
6111
  async (params) => {
@@ -5688,10 +6142,10 @@ function registerGetSemanticLayerGenerationStatus(server, client) {
5688
6142
  }
5689
6143
 
5690
6144
  // src/tools/widgets/list-widgets.ts
5691
- import { z as z29 } from "zod";
6145
+ import { z as z30 } from "zod";
5692
6146
  function widgetReferenceId(widget) {
5693
- for (const candidate of [widget.externalMetricId, widget.id, widget._id, widget.metricId]) {
5694
- if (typeof candidate === "string" && candidate.trim().length > 0) return candidate.trim();
6147
+ for (const candidate2 of [widget.externalMetricId, widget.id, widget._id, widget.metricId]) {
6148
+ if (typeof candidate2 === "string" && candidate2.trim().length > 0) return candidate2.trim();
5695
6149
  }
5696
6150
  return void 0;
5697
6151
  }
@@ -5701,10 +6155,10 @@ function registerListWidgets(server, client) {
5701
6155
  "list_widgets",
5702
6156
  "List widgets on a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous). Returns each widget's externalMetricId (uuid, used for update/remove), metricId (slug), name, published state, and creator identifier.",
5703
6157
  {
5704
- clientId: z29.string().describe("Client/tenant identifier for this dashboard"),
5705
- dashboardName: z29.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5706
- workspaceName: z29.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5707
- embedId: z29.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier.")
6158
+ clientId: z30.string().describe("Client/tenant identifier for this dashboard"),
6159
+ dashboardName: z30.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6160
+ workspaceName: z30.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6161
+ embedId: z30.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier.")
5708
6162
  },
5709
6163
  READ_ONLY,
5710
6164
  async (params) => {
@@ -5768,29 +6222,29 @@ function registerListWidgets(server, client) {
5768
6222
  }
5769
6223
 
5770
6224
  // src/tools/widgets/add-widget.ts
5771
- import { z as z30 } from "zod";
6225
+ import { z as z31 } from "zod";
5772
6226
  function registerAddWidget(server, client) {
5773
6227
  const services = createServiceBundle(client);
5774
6228
  server.tool(
5775
6229
  "add_widget",
5776
6230
  "Add a widget to a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous). Cloning via `templateMetricId` is the recommended path \u2014 query, chart config, and datasource are all inherited. Data app and datasource auto-resolve; never ask the user for them.",
5777
6231
  {
5778
- clientId: z30.string().describe("Client/tenant identifier (widget is created in their scope)"),
5779
- dashboardName: z30.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5780
- workspaceName: z30.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5781
- embedId: z30.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
5782
- dataAppName: z30.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
5783
- templateMetricId: z30.string().optional().describe("Source widget metricId to clone from. Copies query, chartOptions, and datasource fields; caller can override via other params."),
5784
- name: z30.string().optional().describe("Widget name (overrides template)"),
5785
- metricId: z30.string().optional().describe("Unique slug for the new widget (auto-generated if omitted)"),
5786
- query: z30.string().optional().describe("SQL query body (overrides template)"),
5787
- chartOptions: z30.record(z30.unknown()).optional().describe("Chart config JSON (chart type, colors, legend, axes). Overrides template."),
5788
- description: z30.string().optional(),
5789
- companyIntegrationId: z30.string().optional().describe("Datasource UUID override. Auto-resolved from `embedId` or sole active datasource; leave blank unless the user explicitly picked one by ID."),
5790
- integrationName: z30.string().optional().describe('Datasource display name (e.g. "Snowflake"). Use this when the user picks a datasource by name \u2014 the id is looked up automatically.'),
5791
- isPublished: z30.boolean().optional().describe("Publish immediately (default true)"),
5792
- body: z30.record(z30.unknown()).optional().describe("Raw createMetric body passthrough. If provided, used as-is (after defaults). Overrides all other params."),
5793
- widgets: z30.array(z30.record(z30.unknown())).min(1).max(50).optional().describe("Batch of raw createMetric bodies. Dashboard attachment is added to each body.")
6232
+ clientId: z31.string().describe("Client/tenant identifier (widget is created in their scope)"),
6233
+ dashboardName: z31.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6234
+ workspaceName: z31.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6235
+ embedId: z31.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6236
+ dataAppName: z31.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
6237
+ templateMetricId: z31.string().optional().describe("Source widget metricId to clone from. Copies query, chartOptions, and datasource fields; caller can override via other params."),
6238
+ name: z31.string().optional().describe("Widget name (overrides template)"),
6239
+ metricId: z31.string().optional().describe("Unique slug for the new widget (auto-generated if omitted)"),
6240
+ query: z31.string().optional().describe("SQL query body (overrides template)"),
6241
+ chartOptions: z31.record(z31.unknown()).optional().describe("Chart config JSON (chart type, colors, legend, axes). Overrides template."),
6242
+ description: z31.string().optional(),
6243
+ companyIntegrationId: z31.string().optional().describe("Datasource UUID override. Auto-resolved from `embedId` or sole active datasource; leave blank unless the user explicitly picked one by ID."),
6244
+ integrationName: z31.string().optional().describe('Datasource display name (e.g. "Snowflake"). Use this when the user picks a datasource by name \u2014 the id is looked up automatically.'),
6245
+ isPublished: z31.boolean().optional().describe("Publish immediately (default true)"),
6246
+ body: z31.record(z31.unknown()).optional().describe("Raw createMetric body passthrough. If provided, used as-is (after defaults). Overrides all other params."),
6247
+ widgets: z31.array(z31.record(z31.unknown())).min(1).max(50).optional().describe("Batch of raw createMetric bodies. Dashboard attachment is added to each body.")
5794
6248
  },
5795
6249
  WRITE_CREATE,
5796
6250
  async (params) => {
@@ -5938,20 +6392,20 @@ Use \`list_widgets\` to verify, or \`update_widget\`/\`remove_widget\` to adjust
5938
6392
  }
5939
6393
 
5940
6394
  // src/tools/widgets/update-widget.ts
5941
- import { z as z31 } from "zod";
6395
+ import { z as z32 } from "zod";
5942
6396
  function registerUpdateWidget(server, client) {
5943
6397
  const services = createServiceBundle(client);
5944
6398
  server.tool(
5945
6399
  "update_widget",
5946
6400
  "Update a widget on a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous) + `externalMetricId` + `set`. Data app auto-resolves from the dashboard.",
5947
6401
  {
5948
- externalMetricId: z31.string().describe("Internal uuid of the widget (externalMetrics.id). Not the user-facing metricId slug."),
5949
- clientId: z31.string().describe("Client/tenant identifier"),
5950
- dashboardName: z31.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
5951
- workspaceName: z31.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
5952
- embedId: z31.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
5953
- dataAppName: z31.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank."),
5954
- set: z31.record(z31.unknown()).describe("Partial patch. Keys can include: name, query, chartOptions, description, isPublished, isLive, selectedGroupBy, drillDownSettings, limit, etc.")
6402
+ externalMetricId: z32.string().describe("Internal uuid of the widget (externalMetrics.id). Not the user-facing metricId slug."),
6403
+ clientId: z32.string().describe("Client/tenant identifier"),
6404
+ dashboardName: z32.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6405
+ workspaceName: z32.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6406
+ embedId: z32.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6407
+ dataAppName: z32.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank."),
6408
+ set: z32.record(z32.unknown()).describe("Partial patch. Keys can include: name, query, chartOptions, description, isPublished, isLive, selectedGroupBy, drillDownSettings, limit, etc.")
5955
6409
  },
5956
6410
  WRITE_IDEMPOTENT,
5957
6411
  async (params) => {
@@ -6014,19 +6468,19 @@ Use \`list_widgets\` to verify.`
6014
6468
  }
6015
6469
 
6016
6470
  // src/tools/widgets/remove-widget.ts
6017
- import { z as z32 } from "zod";
6471
+ import { z as z33 } from "zod";
6018
6472
  function registerRemoveWidget(server, client) {
6019
6473
  const services = createServiceBundle(client);
6020
6474
  server.tool(
6021
6475
  "remove_widget",
6022
6476
  "Remove a widget from a customer dashboard. Non-technical flow: pass `clientId` + `dashboardName` (+ optional `workspaceName` if ambiguous) + `externalMetricId`. Per-client soft delete \u2014 the widget stays on the template dashboard and on other customers' dashboards.",
6023
6477
  {
6024
- externalMetricId: z32.string().describe("Internal uuid of the widget (externalMetrics.id)"),
6025
- clientId: z32.string().describe("Client/tenant identifier \u2014 the widget will be hidden for this client only"),
6026
- dashboardName: z32.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6027
- workspaceName: z32.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6028
- embedId: z32.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6029
- dataAppName: z32.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank.")
6478
+ externalMetricId: z33.string().describe("Internal uuid of the widget (externalMetrics.id)"),
6479
+ clientId: z33.string().describe("Client/tenant identifier \u2014 the widget will be hidden for this client only"),
6480
+ dashboardName: z33.string().optional().describe("Dashboard name (non-technical). Preferred input \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6481
+ workspaceName: z33.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6482
+ embedId: z33.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6483
+ dataAppName: z33.string().optional().describe("Technical override. Auto-resolved from the dashboard; non-technical callers should leave this blank.")
6030
6484
  },
6031
6485
  DESTRUCTIVE,
6032
6486
  async (params) => {
@@ -6082,21 +6536,21 @@ Note: this is a per-client soft delete. The widget remains available for other c
6082
6536
  }
6083
6537
 
6084
6538
  // src/tools/widgets/generate-widget.ts
6085
- import { z as z33 } from "zod";
6539
+ import { z as z34 } from "zod";
6086
6540
  function registerGenerateWidget(server, client) {
6087
6541
  const services = createServiceBundle(client);
6088
6542
  const tool = server.tool(
6089
6543
  "generate_widget",
6090
6544
  "AI-generate a widget draft from a natural-language prompt. Does NOT persist \u2014 returns a draft to pass to `add_widget`. Non-technical flow: `clientId` + `dashboardName` (+ `workspaceName` if ambiguous); data app and datasource auto-resolve. Never ask the user for data app, datasource, or integration.",
6091
6545
  {
6092
- clientId: z33.string().describe("Client/tenant identifier"),
6093
- userInputs: z33.record(z33.unknown()).describe('Prompt + context object. Typical shape: { prompt: "calls per day over last 30 days", schema: {...}, examples: [...] }. Backend decides which keys it honors.'),
6094
- dashboardName: z33.string().optional().describe("Dashboard name (non-technical). Preferred input for CS/CX flows \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6095
- workspaceName: z33.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6096
- embedId: z33.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6097
- dataAppName: z33.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
6098
- integrationId: z33.string().optional().describe("Technical override \u2014 the datasource UUID. Non-technical callers must leave this blank; the datasource is inherited from the dashboard's existing widgets."),
6099
- integrationName: z33.string().optional().describe("Technical override \u2014 datasource display name. Non-technical callers must leave this blank; the datasource is inherited from the dashboard's existing widgets.")
6546
+ clientId: z34.string().describe("Client/tenant identifier"),
6547
+ userInputs: z34.record(z34.unknown()).describe('Prompt + context object. Typical shape: { prompt: "calls per day over last 30 days", schema: {...}, examples: [...] }. Backend decides which keys it honors.'),
6548
+ dashboardName: z34.string().optional().describe("Dashboard name (non-technical). Preferred input for CS/CX flows \u2014 pass this with optional `workspaceName` instead of `embedId`."),
6549
+ workspaceName: z34.string().optional().describe("Workspace name (non-technical). Only needed if `dashboardName` is ambiguous across workspaces."),
6550
+ embedId: z34.string().optional().describe("Technical alternative to `dashboardName`. Pass only if you already have the embed identifier."),
6551
+ dataAppName: z34.string().optional().describe("Technical override. Auto-resolved from the embed; non-technical callers should leave this blank."),
6552
+ integrationId: z34.string().optional().describe("Technical override \u2014 the datasource UUID. Non-technical callers must leave this blank; the datasource is inherited from the dashboard's existing widgets."),
6553
+ integrationName: z34.string().optional().describe("Technical override \u2014 datasource display name. Non-technical callers must leave this blank; the datasource is inherited from the dashboard's existing widgets.")
6100
6554
  },
6101
6555
  READ_ONLY,
6102
6556
  async (params) => {
@@ -6181,9 +6635,9 @@ function registerGenerateWidget(server, client) {
6181
6635
  }
6182
6636
 
6183
6637
  // src/tools/widgets/create-or-update-metric-from-query.ts
6184
- import { z as z34 } from "zod";
6185
- var modeSchema = z34.enum(["dry_run", "apply"]).default("dry_run");
6186
- var conflictStrategySchema = z34.enum(["fail", "update", "create_new"]).default("fail");
6638
+ import { z as z35 } from "zod";
6639
+ var modeSchema = z35.enum(["dry_run", "apply"]).default("dry_run");
6640
+ var conflictStrategySchema = z35.enum(["fail", "update", "create_new"]).default("fail");
6187
6641
  function makePlanText(plan) {
6188
6642
  return JSON.stringify(plan, null, 2);
6189
6643
  }
@@ -6200,22 +6654,22 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
6200
6654
  "[EMBEDDING] Create or update a production metric/widget on a customer dashboard from validated SQL. Requires clientId (multi-tenant embed). Defaults to dry-run; apply requires explicit confirmation.",
6201
6655
  {
6202
6656
  mode: modeSchema.describe("dry_run returns the plan only; apply executes after confirmation"),
6203
- confirm: z34.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
6657
+ confirm: z35.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
6204
6658
  conflictStrategy: conflictStrategySchema.describe("What to do if metricId already exists on the target dashboard"),
6205
- clientId: z34.string().describe("Client/tenant identifier for the dashboard widget scope"),
6206
- workspaceName: z34.string().describe("Target workspace name"),
6207
- dashboardName: z34.string().optional().describe("Dashboard name to attach to"),
6208
- embedId: z34.string().optional().describe("Technical embed id alternative to dashboardName"),
6209
- dataAppName: z34.string().optional().describe("Optional data app override"),
6210
- name: z34.string().describe("Metric/widget display name"),
6211
- metricId: z34.string().describe("Stable metric slug to create or update"),
6212
- query: z34.string().describe("SQL query already validated against the warehouse"),
6213
- description: z34.string().optional().describe("Metric description"),
6214
- chartOptions: z34.record(z34.unknown()).optional().describe("Chart configuration JSON"),
6215
- companyIntegrationId: z34.string().optional().describe("Datasource UUID override"),
6216
- integrationName: z34.string().optional().describe("Datasource display name override"),
6217
- isPublished: z34.boolean().optional().describe("Publish after apply. Defaults to false for production safety"),
6218
- requestedBy: z34.string().optional().describe("Human operator/request context to store on the metric when supported")
6659
+ clientId: z35.string().describe("Client/tenant identifier for the dashboard widget scope"),
6660
+ workspaceName: z35.string().describe("Target workspace name"),
6661
+ dashboardName: z35.string().optional().describe("Dashboard name to attach to"),
6662
+ embedId: z35.string().optional().describe("Technical embed id alternative to dashboardName"),
6663
+ dataAppName: z35.string().optional().describe("Optional data app override"),
6664
+ name: z35.string().describe("Metric/widget display name"),
6665
+ metricId: z35.string().describe("Stable metric slug to create or update"),
6666
+ query: z35.string().describe("SQL query already validated against the warehouse"),
6667
+ description: z35.string().optional().describe("Metric description"),
6668
+ chartOptions: z35.record(z35.unknown()).optional().describe("Chart configuration JSON"),
6669
+ companyIntegrationId: z35.string().optional().describe("Datasource UUID override"),
6670
+ integrationName: z35.string().optional().describe("Datasource display name override"),
6671
+ isPublished: z35.boolean().optional().describe("Publish after apply. Defaults to false for production safety"),
6672
+ requestedBy: z35.string().optional().describe("Human operator/request context to store on the metric when supported")
6219
6673
  },
6220
6674
  WRITE_IDEMPOTENT,
6221
6675
  async (params) => {
@@ -6375,8 +6829,8 @@ function registerCreateOrUpdateMetricFromQuery(server, client) {
6375
6829
  }
6376
6830
 
6377
6831
  // src/tools/widgets/create-workspace-metric.ts
6378
- import { z as z35 } from "zod";
6379
- var modeSchema2 = z35.enum(["dry_run", "apply"]).default("dry_run");
6832
+ import { z as z36 } from "zod";
6833
+ var modeSchema2 = z36.enum(["dry_run", "apply"]).default("dry_run");
6380
6834
  function registerCreateWorkspaceMetric(server, client) {
6381
6835
  server.tool(
6382
6836
  "create_workspace_metric",
@@ -6385,18 +6839,18 @@ function registerCreateWorkspaceMetric(server, client) {
6385
6839
  mode: modeSchema2.describe(
6386
6840
  "dry_run validates the SQL only (no write); apply creates or updates the metric after confirmation"
6387
6841
  ),
6388
- confirm: z35.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
6389
- workspaceName: z35.string().describe("Target workspace name"),
6390
- name: z35.string().describe("Metric display name"),
6391
- query: z35.string().describe(
6842
+ confirm: z36.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
6843
+ workspaceName: z36.string().describe("Target workspace name"),
6844
+ name: z36.string().describe("Metric display name"),
6845
+ query: z36.string().describe(
6392
6846
  "SQL query to validate and store. In a TABLE-tenancy workspace never compare the client column to a fixed client value; use `client_id_variable` so each client sees only its own rows (the backend refuses it with TENANT_KEY_LITERAL)."
6393
6847
  ),
6394
- metricId: z35.string().optional().describe(
6848
+ metricId: z36.string().optional().describe(
6395
6849
  "Stable metric slug. Pass an existing slug to UPDATE that metric in place. Omit to create \u2014 the slug is derived deterministically from the name (no random/uniqueness suffix), so re-running with the same name upserts the same metric rather than creating a duplicate."
6396
6850
  ),
6397
- description: z35.string().optional().describe("Metric description"),
6398
- datasourceId: z35.string().optional().describe("Datasource UUID override; defaults to the workspace datasource"),
6399
- isPublished: z35.boolean().optional().describe(
6851
+ description: z36.string().optional().describe("Metric description"),
6852
+ datasourceId: z36.string().optional().describe("Datasource UUID override; defaults to the workspace datasource"),
6853
+ isPublished: z36.boolean().optional().describe(
6400
6854
  "Publish after apply. Defaults to false. Applies to creates; on updates the existing publish state is preserved."
6401
6855
  )
6402
6856
  },
@@ -6452,25 +6906,25 @@ function registerCreateWorkspaceMetric(server, client) {
6452
6906
  }
6453
6907
 
6454
6908
  // src/tools/widgets/validate-metric-spec.ts
6455
- import { z as z36 } from "zod";
6909
+ import { z as z37 } from "zod";
6456
6910
  function registerValidateMetricSpec(server, client) {
6457
6911
  const services = createServiceBundle(client);
6458
6912
  server.tool(
6459
6913
  "validate_metric_spec",
6460
6914
  "Validate a production metric spec from SQL without saving it. Resolves dashboard, data app, datasource, conflicts, and required fields.",
6461
6915
  {
6462
- clientId: z36.string().describe("Client/tenant identifier used to validate dashboard widget scope"),
6463
- workspaceName: z36.string().describe("Target production-backed workspace name"),
6464
- dashboardName: z36.string().optional().describe("Dashboard name to attach to"),
6465
- embedId: z36.string().optional().describe("Technical embed id alternative to dashboardName"),
6466
- dataAppName: z36.string().optional().describe("Optional data app override"),
6467
- name: z36.string().describe("Metric/widget display name"),
6468
- metricId: z36.string().describe("Stable metric slug to create or update"),
6469
- query: z36.string().describe("SQL query already validated against the warehouse"),
6470
- description: z36.string().optional().describe("Metric description"),
6471
- chartOptions: z36.record(z36.unknown()).optional().describe("Chart configuration JSON"),
6472
- companyIntegrationId: z36.string().optional().describe("Datasource UUID override"),
6473
- integrationName: z36.string().optional().describe("Datasource display name override")
6916
+ clientId: z37.string().describe("Client/tenant identifier used to validate dashboard widget scope"),
6917
+ workspaceName: z37.string().describe("Target production-backed workspace name"),
6918
+ dashboardName: z37.string().optional().describe("Dashboard name to attach to"),
6919
+ embedId: z37.string().optional().describe("Technical embed id alternative to dashboardName"),
6920
+ dataAppName: z37.string().optional().describe("Optional data app override"),
6921
+ name: z37.string().describe("Metric/widget display name"),
6922
+ metricId: z37.string().describe("Stable metric slug to create or update"),
6923
+ query: z37.string().describe("SQL query already validated against the warehouse"),
6924
+ description: z37.string().optional().describe("Metric description"),
6925
+ chartOptions: z37.record(z37.unknown()).optional().describe("Chart configuration JSON"),
6926
+ companyIntegrationId: z37.string().optional().describe("Datasource UUID override"),
6927
+ integrationName: z37.string().optional().describe("Datasource display name override")
6474
6928
  },
6475
6929
  READ_ONLY,
6476
6930
  async (params) => {
@@ -6561,20 +7015,20 @@ function registerValidateMetricSpec(server, client) {
6561
7015
  }
6562
7016
 
6563
7017
  // src/tools/reports/list-scheduled-reports.ts
6564
- import { z as z37 } from "zod";
7018
+ import { z as z38 } from "zod";
6565
7019
  function registerListScheduledReports(server, client) {
6566
7020
  const services = createServiceBundle(client);
6567
7021
  server.tool(
6568
7022
  "list_scheduled_reports",
6569
7023
  "List configured scheduled reports for a specific client/dashboard embed. Shows schedule metadata, not delivery success history.",
6570
7024
  {
6571
- clientId: z37.string().describe("Client/tenant identifier. Required by the current backend API for non-empty results"),
6572
- dashboardName: z37.string().optional().describe("Dashboard name to inspect"),
6573
- workspaceName: z37.string().optional().describe("Workspace name if dashboardName is ambiguous"),
6574
- embedId: z37.string().optional().describe("Technical embed id alternative to dashboardName"),
6575
- userIdentifier: z37.string().optional().describe("Optional creator/user filter"),
6576
- pageNumber: z37.number().int().positive().optional().describe("Page number when pagination is enabled"),
6577
- isPagination: z37.boolean().optional().describe("Enable backend pagination")
7025
+ clientId: z38.string().describe("Client/tenant identifier. Required by the current backend API for non-empty results"),
7026
+ dashboardName: z38.string().optional().describe("Dashboard name to inspect"),
7027
+ workspaceName: z38.string().optional().describe("Workspace name if dashboardName is ambiguous"),
7028
+ embedId: z38.string().optional().describe("Technical embed id alternative to dashboardName"),
7029
+ userIdentifier: z38.string().optional().describe("Optional creator/user filter"),
7030
+ pageNumber: z38.number().int().positive().optional().describe("Page number when pagination is enabled"),
7031
+ isPagination: z38.boolean().optional().describe("Enable backend pagination")
6578
7032
  },
6579
7033
  READ_ONLY,
6580
7034
  async (params) => {
@@ -6684,19 +7138,19 @@ function registerWhoami(server, client) {
6684
7138
  }
6685
7139
 
6686
7140
  // src/tools/custom-chart/scaffold.ts
6687
- import { z as z39 } from "zod";
7141
+ import { z as z40 } from "zod";
6688
7142
 
6689
7143
  // src/tools/custom-chart/shared.ts
6690
- import { z as z38 } from "zod";
7144
+ import { z as z39 } from "zod";
6691
7145
  var slotInputSchema = slotSchema;
6692
7146
  var optionInputSchema = optionSchema;
6693
- var manifestInputSchema = z38.record(z38.unknown()).describe("manifest.json as an object (schemaVersion 2). Validated with the same rules as the server.");
7147
+ var manifestInputSchema = z39.record(z39.unknown()).describe("manifest.json as an object (schemaVersion 2). Validated with the same rules as the server.");
6694
7148
  var bundleInputShape = {
6695
7149
  manifest: manifestInputSchema.optional(),
6696
- bundleJs: z38.string().optional().describe("dist/index.js from `databrain custom-chart build` (the shim plus the chart module)."),
6697
- bundleCss: z38.string().optional().describe("The raw chart.css, kept for provenance."),
6698
- iconSvg: z38.string().optional().describe("icon.svg, at most 16 KiB, no script, no foreignObject, no external href."),
6699
- projectDir: z38.string().optional().describe("Local only: a chart project folder. The tool reads it instead of manifest/bundleJs/iconSvg.")
7150
+ bundleJs: z39.string().optional().describe("dist/index.js from `databrain custom-chart build` (the shim plus the chart module)."),
7151
+ bundleCss: z39.string().optional().describe("The raw chart.css, kept for provenance."),
7152
+ iconSvg: z39.string().optional().describe("icon.svg, at most 16 KiB, no script, no foreignObject, no external href."),
7153
+ projectDir: z39.string().optional().describe("Local only: a chart project folder. The tool reads it instead of manifest/bundleJs/iconSvg.")
6700
7154
  };
6701
7155
  function formatProblems(problems) {
6702
7156
  if (problems.length === 0) return "No problems.";
@@ -6763,11 +7217,11 @@ function registerCustomChartScaffold(server, _client) {
6763
7217
  "custom_chart_scaffold",
6764
7218
  "Return the files of a new custom chart project (manifest.json, chart.ts, chart.css, icon.svg, sample data, guide). Nothing is written or persisted; write the files yourself.",
6765
7219
  {
6766
- key: z39.string().describe("Chart key, becomes chart type custom:<key>. Lowercase letters, digits, _ and -, 64 max."),
6767
- name: z39.string().describe("Display name shown in the chart picker, 1 to 256 characters."),
6768
- description: z39.string().optional().describe("Optional description, up to 500 characters."),
6769
- slots: z39.array(slotInputSchema).optional().describe("Data slots. Defaults to category (dimension), value (measure), target (optional measure)."),
6770
- options: z39.array(optionInputSchema).optional().describe("Chart options. Defaults to the template options (labels, subtitle, radius, colour, sort).")
7220
+ key: z40.string().describe("Chart key, becomes chart type custom:<key>. Lowercase letters, digits, _ and -, 64 max."),
7221
+ name: z40.string().describe("Display name shown in the chart picker, 1 to 256 characters."),
7222
+ description: z40.string().optional().describe("Optional description, up to 500 characters."),
7223
+ slots: z40.array(slotInputSchema).optional().describe("Data slots. Defaults to category (dimension), value (measure), target (optional measure)."),
7224
+ options: z40.array(optionInputSchema).optional().describe("Chart options. Defaults to the template options (labels, subtitle, radius, colour, sort).")
6771
7225
  },
6772
7226
  READ_ONLY,
6773
7227
  async (params) => {
@@ -6859,18 +7313,18 @@ function registerCustomChartValidate(server, client) {
6859
7313
  }
6860
7314
 
6861
7315
  // src/tools/custom-chart/upload.ts
6862
- import { z as z40 } from "zod";
7316
+ import { z as z41 } from "zod";
6863
7317
  function registerCustomChartUpload(server, client) {
6864
7318
  const api = createCustomChartsApi(client);
6865
7319
  server.tool(
6866
7320
  "custom_chart_upload",
6867
7321
  "Upload a built custom chart bundle to the company. Creates the chart (private, version 1) or a new preview version. Defaults to dry_run; apply requires explicit confirmation and can publish in the same call.",
6868
7322
  {
6869
- mode: z40.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run validates and returns the plan; apply uploads."),
6870
- confirm: z40.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
6871
- publish: z40.boolean().default(false).describe("After a successful apply, publish the uploaded version so every builder can use it."),
6872
- name: z40.string().optional().describe("Display name override. Defaults to manifest.name."),
6873
- description: z40.string().optional().describe("Description override. Defaults to manifest.description."),
7323
+ mode: z41.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run validates and returns the plan; apply uploads."),
7324
+ confirm: z41.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`),
7325
+ publish: z41.boolean().default(false).describe("After a successful apply, publish the uploaded version so every builder can use it."),
7326
+ name: z41.string().optional().describe("Display name override. Defaults to manifest.name."),
7327
+ description: z41.string().optional().describe("Description override. Defaults to manifest.description."),
6874
7328
  ...bundleInputShape
6875
7329
  },
6876
7330
  WRITE_CREATE,
@@ -7011,17 +7465,17 @@ function registerListCustomCharts(server, client) {
7011
7465
  }
7012
7466
 
7013
7467
  // src/tools/custom-chart/publish.ts
7014
- import { z as z41 } from "zod";
7468
+ import { z as z42 } from "zod";
7015
7469
  function registerPublishCustomChart(server, client) {
7016
7470
  const api = createCustomChartsApi(client);
7017
7471
  server.tool(
7018
7472
  "publish_custom_chart",
7019
7473
  "Publish a custom chart version so every builder in the company can use chart type custom:<key>. Defaults to the preview version. Defaults to dry_run; apply requires explicit confirmation.",
7020
7474
  {
7021
- key: z41.string().describe("The chart key (manifest.id), for example revenue-bars."),
7022
- versionId: z41.string().optional().describe("A specific version id to publish. Defaults to the preview version."),
7023
- mode: z41.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run shows what would be published; apply publishes."),
7024
- confirm: z41.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`)
7475
+ key: z42.string().describe("The chart key (manifest.id), for example revenue-bars."),
7476
+ versionId: z42.string().optional().describe("A specific version id to publish. Defaults to the preview version."),
7477
+ mode: z42.enum(["dry_run", "apply"]).default("dry_run").describe("dry_run shows what would be published; apply publishes."),
7478
+ confirm: z42.string().optional().describe(`Required for apply: ${APPLY_TO_PRODUCTION_CONFIRMATION}`)
7025
7479
  },
7026
7480
  WRITE_IDEMPOTENT,
7027
7481
  async (params) => {
@@ -7132,6 +7586,12 @@ function registerAllTools(server, client) {
7132
7586
  registerUpdateSemanticLayer(server, client);
7133
7587
  registerStartSemanticLayerGeneration(server, client);
7134
7588
  registerGetSemanticLayerGenerationStatus(server, client);
7589
+ registerSearchSemanticModel(server, client);
7590
+ registerGetSemanticEntity(server, client);
7591
+ registerProposeSemanticChange(server, client);
7592
+ registerApplySemanticChange(server, client);
7593
+ registerEvalSemanticModel(server, client);
7594
+ registerSemanticReadiness(server, client);
7135
7595
  registerListWidgets(server, client);
7136
7596
  registerValidateMetricSpec(server, client);
7137
7597
  registerCreateOrUpdateMetricFromQuery(server, client);
@@ -9026,6 +9486,20 @@ When the same flag is set at multiple levels, the highest precedence wins:
9026
9486
 
9027
9487
  // src/resources/content/semantic-layer-guide.ts
9028
9488
  var semanticLayerGuideContent = `
9489
+
9490
+ ## Semantic models v2
9491
+
9492
+ The v2 source is one YAML document. Use the configured service credential and a datamart ID from \`list_datamarts\`.
9493
+
9494
+ 1. \`search_semantic_model\` finds entities by question. \`get_semantic_entity\` reads one entity by kind and name. Column names use table.column.
9495
+ 2. \`propose_semantic_change\` takes complete source YAML, a YAML patch or one structured change. It returns candidate source, validation errors, a plan and the stored expected hash without writing.
9496
+ 3. \`eval_semantic_model\` evaluates the proposed source. \`semantic_readiness\` returns the server score, verdict and open checks.
9497
+ 4. \`apply_semantic_change\` defaults to a dry run. Live apply requires the exact reviewed source, \`expected_content_hash\`, \`dry_run: false\` and user supplied \`confirm: "APPLY_TO_PRODUCTION"\`. Never supply confirmation yourself. Apply changes the live model for all consumers. On a conflict, prepare a fresh proposal and review it again.
9498
+
9499
+ YAML patches merge mappings and named lists by name. Other lists replace. Empty lists clear; null removes a field. Structured changes use operation upsert or remove, kind, name, and value for upsert. Column changes also require table. Patch output normalizes formatting and comments, so review the returned source before apply.
9500
+
9501
+ The legacy maintenance workflow below applies to datamarts that have not moved to v2. Do not call \`update_semantic_layer\` for v2.
9502
+
9029
9503
  # Databrain Semantic Layer Guide
9030
9504
 
9031
9505
  ## What Is the Semantic Layer?
@@ -10012,6 +10486,9 @@ If **no datamarts exist**, tell the user they need to create one in the **Databr
10012
10486
 
10013
10487
  If **no datasources exist either**, the user must first connect their database in the **Databrain UI**.
10014
10488
 
10489
+ **For a v2 semantic model**
10490
+ If the datamart uses v2, inspect the question's entities with \`search_semantic_model\` and \`get_semantic_entity\`. Read \`semantic_readiness\` for open checks, then continue to Step 2. If metadata needs correction, use \`propose_semantic_change\`, evaluate its source with \`eval_semantic_model\`, and show the plan. Only apply the reviewed source and expected hash through \`apply_semantic_change\` after the user supplies \`APPLY_TO_PRODUCTION\`. Never use the legacy \`update_semantic_layer\` steps below for v2.
10491
+
10015
10492
  **Step 1b \u2014 Verify the Semantic Layer**
10016
10493
  After confirming a datamart exists, use \`get_semantic_layer\` to check its tables. The semantic layer is built on the **datamart** (not the raw datasource) \u2014 do NOT use \`discover_datasource_schema\` to populate it.
10017
10494
 
@@ -10093,6 +10570,15 @@ If the confidence is low or results seem wrong:
10093
10570
  // src/prompts/populate-semantic-layer.ts
10094
10571
  var populateSemanticLayerPrompt = `You are helping the user populate the semantic layer for a Databrain datamart. The semantic layer provides table/column descriptions, synonyms, and context that the AI needs to convert natural language questions into accurate SQL.
10095
10572
 
10573
+ **For a v2 semantic model**
10574
+ If \`list_datamarts\` reports \`semanticLayerVersion: "v2"\`, or the selected datamart is already known to use v2, use this workflow instead of the legacy steps below:
10575
+ 1. Inspect relevant entities with \`search_semantic_model\` and \`get_semantic_entity\`, using the datamart ID.
10576
+ 2. Call \`propose_semantic_change\` with a YAML patch or structured change. Show its errors and plan. Correct errors before continuing.
10577
+ 3. Call \`eval_semantic_model\` with the returned source. Show the verdicts and let the user review the candidate.
10578
+ 4. Stop for the user's explicit \`APPLY_TO_PRODUCTION\` confirmation. Never supply it yourself.
10579
+ 5. Call \`apply_semantic_change\` with the exact reviewed source, its \`expected_content_hash\`, \`dry_run: false\` and the user's confirmation. A conflict requires a new proposal and review.
10580
+ 6. Call \`semantic_readiness\` and inspect the changed entity. Do not call \`update_semantic_layer\` for a v2 model.
10581
+
10096
10582
  **Why this matters:** Without a populated semantic layer, \`ask_question\` cannot generate SQL \u2014 every query will fail with "out of context." Populating the semantic layer is the critical step between having a datamart and being able to query it.
10097
10583
 
10098
10584
  **Step 1 \u2014 Identify the Datamart**
@@ -10477,4 +10963,4 @@ export {
10477
10963
  registerAllResources,
10478
10964
  registerAllPrompts
10479
10965
  };
10480
- //# sourceMappingURL=chunk-Z2SF3K6P.js.map
10966
+ //# sourceMappingURL=chunk-B7HJRMR2.js.map