New to Astrato Step 2 of 4
Next →Semantic layer workflows
Use the Nash MCP gateway to inspect and change semantic layers without editing the stored document directly. Describe a change in business terms and the server resolves it against the model, builds the definition, validates it, and protects the write with a revision check.
Connect your MCP client by following MCP setup. Semantic-layer reads require
sl:read, edits require sl:write, and creating versions or importing a Snowflake semantic
view also requires sl:version.
Return to your workbook from the editor
Section titled “Return to your workbook from the editor”When you open the Semantic Layer Editor from a workbook, Back to Workbook in the header returns to that workbook and its originating sheet. The context stays available while you move between editor tabs and after refreshing the page, including when several workbooks use the same semantic layer. Opening the editor directly shows Create Workbook instead.
Period-over-period aggregate formulas
Section titled “Period-over-period aggregate formulas”A period comparison can use arithmetic over field aggregates — the editor shows such a measure
as SUM(Salesamount) / COUNT(DISTINCT SalesOrderNumber), which is stored as expression: '{m1} / {m2}' with each aggregate in its own m1/m2 block. Create a Time intelligence axis dimension,
then enable Period over period settings on the measure’s Advanced tab and
select that axis. Each period evaluates the ratio from its separately aggregated
numerator and denominator; it does not average individual row ratios.
The formula is evaluated at the requested result grain. With a daily axis, each
row compares daily values; without a visible axis, the KPI recalculates the formula
over the whole selected period. A customer appearing on several days is counted
once in that period’s distinct customer count. Daily ratios are not added together
to produce the KPI. Growth is (current - previous) / ABS(previous); set a
percentage format to display it as a percentage.
Whole-period comparisons without a visible axis do not support Time intelligence axis ranges whose boundary is the source data’s minimum or maximum date.
Use an explicit nestedCalculation when you want a reduction of per-bucket values.
They are different from an ordinary period comparison: a nested SUM, for example,
adds values calculated at its additional dimension’s grain. See Period axes and comparisons
for the aligned YAML definition and Business Questions feature availability.
A comparison is computed over aggregate arithmetic. A formula that references other measures — selecting one, or combining several — is supported when every referenced measure is itself such an aggregate: it is computed over the aggregates they name. Unaggregated fields, conditional formulas, and references to measures that carry their own comparison or declare parameters are not supported as comparison bases.
Start with live context
Section titled “Start with live context”When your authorization includes sl:read, call astrato_get_context at the start of the session.
It returns:
- your permissions and scopes;
- the configured default semantic layer, if one exists;
- the current draft revision and how revision checks work;
- the supported authoring workflows.
A data-only client holding data:query cannot call astrato_get_context. Use tools/list and the
server-provided instructions, then start the data flow with astrato_data_prepare_query.
If you already know which layer to use, pass its semanticLayerId to every subsequent tool that
accepts it. Session-scoped calls carry the selected layer through sessionId. If the layer is not
known, use the default returned by astrato_get_context. When there is no default, call
astrato_semantic_layer_list, review the available layers, and select the intended one. If it is not
clear which layer applies, stop and ask before continuing. The list is paginated: follow
pagination.nextOffset while pagination.hasMore is true when the requested layer is not in the
current page. Do not switch layers implicitly.
Inspect progressively
Section titled “Inspect progressively”Use the smallest read that answers the question:
astrato_semantic_layer_searchfor a focused question about a view, field, join, measure, dimension, or function.astrato_semantic_layer_get_summaryfor counts and current revision.astrato_semantic_layer_get_detailsfor one paginated collection. FollownextCursoronly when you need more results.astrato_semantic_layer_validatewhen you need to check correctness and before treating an edit as complete.
Do not fetch the entire model for a focused change. The prepare tools use the same ranked field
matching as astrato_semantic_layer_search_fields, so there is no need to search first when prepare already resolves
the requested fields.
Read the complete V3 document
Section titled “Read the complete V3 document”Deterministic integrations that need the whole model use the snapshot returned by
astrato_semantic_layer_get_summary instead of paginating every detail collection. The snapshot is the
complete current draft as Semantic Layer V3 JSON, including measure definitions that compact detail
pages intentionally omit.
After initializing the MCP connection, send this JSON-RPC request:
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "astrato_semantic_layer_get_summary", "arguments": { "semanticLayerId": "11111111-1111-4111-8111-111111111111", "includeSnapshot": true } }}Find the item in result.content whose type is resource_link, then pass its uri to
resources/read:
{ "jsonrpc": "2.0", "id": 2, "method": "resources/read", "params": { "uri": "nash-mcp://resources/<resource-id>" }}Read the resource in the same MCP session that created the link. Parse result.contents[0].text as
JSON; it is the complete fileVersion: 3 document. The text result also contains these artifact
fields:
filename,mimeTypeand UTF-8size;snapshotRevision, the exact draft revision in the resource;publishedRevision, ornullwhen the layer has never been published;publicationState:published,unpublished_changes,never_published, orsuperseded.
The text result’s data.resourceUri and data.snapshotResourceUri contain the same URI when the
attachment is requested. Without includeSnapshot: true, the server does not serialize or store a
snapshot and neither URI is present. Omit the flag for ordinary summaries and bounded detail reads.
superseded means a newer revision was published while this point-in-time snapshot was being
prepared. Read the summary again before claiming that snapshot is current. The resource is JSON;
serialize it to YAML locally when a YAML artifact is required.
Choose the edit workflow
Section titled “Choose the edit workflow”To create a semantic layer that does not exist yet, call astrato_data_list_sources, choose the exact
datasourceId, then call astrato_semantic_layer_create. The new layer is empty and unpublished.
Astrato may rename it to avoid a collision, so use the returned name in the response and pass the
returned semanticLayerId to every later authoring call.
| Requested change | Workflow |
|---|---|
| One measure | astrato_semantic_layer_prepare_measure_change → astrato_semantic_layer_apply_measure_change |
| One field, bucket, or drilldown dimension | astrato_semantic_layer_prepare_dimension_change → astrato_semantic_layer_apply_dimension_change |
| Several independent measures or dimensions | astrato_semantic_layer_plan_authoring_batch → astrato_semantic_layer_commit_authoring_batch |
| One or more view and field changes | Optional source search → astrato_semantic_layer_prepare_view_change → astrato_semantic_layer_apply_view_change |
| A supported join, function, or description edit | astrato_semantic_layer_begin_edit_session → astrato_semantic_layer_apply_edit → astrato_semantic_layer_preview_edit_session → astrato_semantic_layer_commit_edit_session |
| Upstream datatype or conservative physical-field rename | astrato_semantic_layer_check_source_schema → confirmation → astrato_semantic_layer_apply_source_schema_fix |
| Snowflake semantic-view import | astrato_semantic_layer_list_snowflake_views → astrato_semantic_layer_preview_snowflake_view_import → confirmation → astrato_semantic_layer_import_snowflake_view |
Do not use a generic edit session for structural measure, dimension, view, or field changes. The
typed tools accept structured business requests and are the supported MCP workflows. A
description-only update, including a dimension description, uses the edit session’s batched
update_descriptions operation.
One measure or dimension
Section titled “One measure or dimension”Prepare once with the complete request and every concept needed by the definition. The result contains field matches, available aggregations, an existing target when applicable, and the current revision. If several matches are plausible, resolve the ambiguity instead of choosing one by guess.
This example prepares a new aggregate measure:
{ "semanticLayerId": "11111111-1111-4111-8111-111111111111", "request": "Create Total Order Amount as the sum of Order Amount", "queries": [ { "id": "orderAmount", "text": "Order Amount", "kinds": ["field"], "dataTypes": ["numeric"] } ]}Pass that object to astrato_semantic_layer_prepare_measure_change. When it finds an exact match and confirms that
SUM is available, apply the returned names and revision in one call:
Set includeSuggestions: true when you also want create-ready measure suggestions from the
semantic engine. Set includeCompiledSql: true to receive statement-only SQL for the prepared
measure; on a create request it returns and compiles the suggested measures. Dimension preparation
supports the same includeSuggestions option. These optional operations use the current draft and
never publish it.
For repeated deterministic measure preparation, set compact: true. The compact response preserves
the revision, exact field and measure references, aggregation capabilities, target definition and
relevant joins. It omits repeated locale, relative-period and authoring-rule metadata, plus candidate
descriptions and duplicate confidence fields. Omit compact when a person or model needs that
display context; the default response is unchanged.
{ "semanticLayerId": "11111111-1111-4111-8111-111111111111", "preparedTarget": null, "expectedRevision": 42, "change": { "operation": "create", "name": "Total Order Amount", "definition": { "description": "Total value of orders.", "inputs": [ { "id": "orderAmount", "kind": "aggregate", "field": { "view": "Orders", "field": "Order Amount" }, "aggregation": "SUM" } ] } }}The UUID and revision above are examples. Use the selected semantic-layer ID, keep the input IDs
consistent between prepare and apply, and copy the exact view, field, aggregation, revision, and
preparedTarget returned by prepare. The apply call validates the change and commits at most one
draft revision. It never publishes the layer.
To aggregate arithmetic over each row, give an aggregate input row instead of field: an
expression over names in braces, and fields mapping each name to its view and field.
{"expression": "{price} * {quantity}", "fields": {"price": {...}, "quantity": {...}}} stores
SUM(Price * Quantity). A row cannot contain a number yet, so write SUM(Price * (1 - Discount))
as two inputs, SUM(Price) and SUM(Price * Discount), with the expression {m1} - {m2}.
A conditional measure is an IF written as the whole expression over the input IDs, for example
IF(sales > 1000000, 'Large', IF(sales > 100000, 'Medium', 'Small')) with one aggregate input
sales. The rules it follows are listed under Stored measure expression rules.
A parameter input takes dataType int or float; it is float when omitted, and an int
default must be a whole number. To change the default of a parameter a measure already declares,
patch it by name without resending the calculation:
{ "operation": "patch", "name": "Rolling Sales", "patch": { "parameters": { "periods": 6 } } }Each parameter keeps its stored type. A name the measure does not declare is refused with the names it does.
A field dimension can group a date or time field by dateLevel (such as month) or extract a
datePart (such as quarter). Use a name from the dateLevels or dateParts that dimension
preparation lists for the datasource; the names vary by database. Names match without regard to
case. A query’s local dimension is checked against the same lists, and so is a date transform in
a query’s or business question’s filter or in the filter of a measure the query defines. A name
the datasource does not offer is refused before the query runs, with the names it does. A stored
measure’s, view’s or dataset’s filter is not checked. dayOfWeek, weekday and dayOfMonth are
not names: use dayofweekiso, which counts Monday as 1 and Sunday as 7, and day for the day of
the month.
A periodAxis dimension (a Time Axis) reads one date field, or several sources when fact
tables that do not join share one axis, such as order date and return date. Each source is
{ "field": { "view": ..., "field": ... } } or { "dimension": ... }, a field dimension over a
date or timestamp field with no date level or part. An axis takes at most one source per view.
A replace keeps what its definition leaves out: the label, synonyms, categories and import
record, and on a measure the aggregate settings the authoring fields do not carry. Send label or
categories to change them. To change one bucket bound or the Time Axis window, patch bucketing
or timeAxis instead of replacing the dimension. A delete takes references: remove deletes
the item with what depends on it, as the editor’s Delete references does, and keep, the
default, leaves those references broken and lists them under publishBlockers. Rename takes references: keep to rename only
the item. duplicate copies a measure or dimension as Copy of its name unless newName is given.
Stored measure expression rules
Section titled “Stored measure expression rules”Use the same expression forms as the visual measure editor:
- Arithmetic supports
+,-,*,/and parentheses, with numeric values, numeric parameters, and compatible aggregates or stored measures. Negative numbers such as-0.1are supported; negating a reference or group, such as-(Revenue + Cost), is not. - One row aggregation can wrap arithmetic over fields, for example
SUM(Price * Quantity). Values, parameters and nested aggregations are not supported inside that row expression. Choose an aggregation from the datasource capabilities; use the aggregation’s distinct setting rather than inventing a function name such asCOUNT_DISTINCT. When the connection’sCOUNTsupports distinct values, a request forCOUNT_DISTINCT,countDistinct,COUNTDorDISTINCT_COUNTis refused with a message that namesCOUNTwithdistinct: true. - The fields of one row expression must sit on one row: one of their views reaches every other
through many-to-one or one-to-one joins, followed in their declared direction. Quantity × a
part’s retail price, over an order line joined many-to-one to its part, passes. Supply cost ×
quantity, across a part-supplier view and order lines with no join from one to the other, is
refused as
cross-grain-row-expression, because the engine would read one side as empty and return a wrong total. Add a many-to-one join on every field that identifies the other view’s row (a composite join), or a custom SQL view that computes the value; for separate totals, aggregate each view in its own operand. An aggregation withfanout: "allow"orfanout: "reduceToRelation"also reaches across many-to-many joins. Both enable fanout;reduceToRelationadditionally reduces to the relation’s dimensionality. Neither bridges two views that only share an attribute, such as both joining to a part. IFrequires a condition, THEN value and ELSE value. Conditions use measure comparisons or null checks, joined with AND/OR and parentheses. Branches can contain compatible measure references, literal values or another IF. Arithmetic and parameters inside IF are not supported, and IF cannot be embedded in arithmetic.- Numeric values must be finite and filled in. Comparison and branch types must be compatible; a null branch needs a non-null alternative. Text, Boolean and null literal comparisons support equality/inequality or null checks.
- General scalar functions such as
DATEDIFF,CURRENT_DATE,ABS,ROUNDandCOALESCEare not stored-measure expression functions. SQL fields and aggregate filters have separate rules.
The examples above describe the calculation; typed authoring expressions refer to the input IDs
provided in definition.inputs. Authoring tools reject unsupported expressions with a measure and
expression-path diagnostic. Manually edited YAML can remain saved as a repairable draft with errors;
publication requires a data connection with available capabilities and those errors to be resolved.
Dimensions use the same pattern. Prepare all field or reusable-dimension concepts together, then
apply one typed field, bucket, or drillthrough definition. The server derives result types
and can resolve omitted numeric bucket ranges from data.
Filter conditions
Section titled “Filter conditions”A filter on an aggregate input, and each rule of a custom-group (mapping) dimension, is a list of
conditions, each naming a field, an operator and a value. They are stored exactly as the editor’s
filter builder stores them. Values are typed from the field, so no data type needs to be supplied.
| Operator | Value |
|---|---|
equals, not_equals, greater_than, greater_than_or_equal, less_than, less_than_or_equal |
one value, another field, or a single date (below) |
contains, not_contains, like, not_like |
one text value |
between, not_between |
two values, [from, to]; between also a date range |
in, not_in |
a list of one or more values |
is_set, is_not_set, hide_not_set |
none; hide_not_set drops null and zero or empty values |
geo_intersects |
a polygon, on a geography field |
A single-value operator given a list is refused; use in or not_in for a list.
like and not_like ignore case and use Astrato’s search syntax, not SQL: * matches any run
of characters, ? matches one character, and \ escapes either. % and _ match themselves, so
LG* keeps text starting with LG and *BRASS text ending with BRASS. A value with no * or ?
matches anywhere in the text, as contains does.
A value written with a SQL % wildcard, such as LG%, would match only text containing “LG%”:
like would keep no rows and not_like every row. astrato_data_execute_query refuses one, and
so does an MCP authoring tool when its edit writes a value the layer does not already hold; each
names the pattern to send instead. An edit that keeps a stored value, such as renaming the measure
or a field it filters on, is not refused. To match a literal
%, use contains or write \%. Layer validation shows the same problem as a warning on
filters already stored.
A custom group rule is the exception: it still compiles like as SQL LIKE, so there % and
_ are the wildcards and * and ? match themselves.
To compare two fields of the same row, give {"kind": "field", "field": {"view": ..., "field": ...}}
with equals, not_equals or one of the four comparisons, for example orders shipped after they
were due. Both fields must be of the same kind (number, date, text or Boolean).
An aggregate filter on another view’s field works only when each row the measure aggregates joins at most one row of that view, directly or through other views. When a row joins many, the filter cannot be evaluated: the measure still saves, but a question that uses it is refused when it executes; see Filters that cannot reach a measure’s rows.
A date or timestamp field also takes a generated date, resolved when the query runs:
{"kind": "relative_period", "period": ...}withbetween:year_to_date,month_to_date,quarter_to_date,week_to_date, theirprevious_…_to_dateforms,extract_year_to_dateandextract_month_to_date(the same to-date span in every year or month),last_12_months,last_90_days,last_30_days,last_7_days,current_year,current_quarter,current_month,current_week,previous_year,previous_quarter,previous_month,previous_week,today,yesterday, andminimum_dateandmaximum_date(the earliest or latest date in the data).{"kind": "predefined_date", "date": ...}withequals,not_equalsor a comparison:beginning_of_year,beginning_of_quarter,beginning_of_month,beginning_of_week, theirbeginning_of_previous_…forms,end_of_year,end_of_quarter,end_of_month,end_of_week,todayandyesterday.{"kind": "relative_date", "unit": "month", "offset": -1}, compared like a predefined date.{"kind": "relative_range", "unit": "day", "from": -7, "to": 0}withbetween;frommust not be afterto.
Units are year, quarter, month, week, day, hour, minute and second. An operator a
date form cannot take is refused with the one to use. A fixed date is written YYYY-MM-DD.
Custom-group rules are compiled into a column once, so they accept fixed dates only.
geo_intersects takes {"kind": "polygon", "points": [[longitude, latitude], ...]}: at least four
points, the last repeating the first, with longitudes from -180 to 180 and latitudes from -90 to 90.
The user attributes $userEmail and $userGroupNames are refused in both places. A stored
measure would compare the parameter’s stored default rather than the requesting user, and a custom
group would compile that default into its column. To restrict rows per viewer, add a security rule
to the table in the editor, where the attribute resolves to the requesting user.
Current-draft engine previews
Section titled “Current-draft engine previews”The editor’s measure SQL view and the optional prepare-tool suggestions use the current draft, so unpublished edits appear immediately, and they never publish it. A draft with blocking validation errors cannot run these operations; correct the reported document issues first.
Compiled SQL is a statement preview only. It does not execute data, perform a new authorization or datasource lookup, or return query warnings and resolved items.
Author several items as one batch
Section titled “Author several items as one batch”Plan independent measure and dimension requests together. For a proposed measure creation, include a measure query for the intended use case so planning can find an exact existing measure instead of creating a duplicate.
{ "semanticLayerId": "11111111-1111-4111-8111-111111111111", "items": [ { "id": "totalOrderAmount", "entity": "measure", "operation": "create", "request": "Create Total Order Amount as the sum of Order Amount", "queries": [ { "id": "existingTotal", "text": "Total Order Amount", "kinds": ["measure"] }, { "id": "orderAmount", "text": "Order Amount", "kinds": ["field"], "dataTypes": ["numeric"] } ] }, { "id": "orderDate", "entity": "dimension", "operation": "create", "request": "Create an Order Date field dimension", "queries": [{ "id": "orderDateField", "text": "Order Date", "kinds": ["field"], "dataTypes": ["date"] }] } ]}Call astrato_semantic_layer_plan_authoring_batch with that object and review the complete plan. Commit only when
every item is ready and the plan has not found an existing item that should be reused. Copy the
returned revision, optional planId, and each preparedTarget into
astrato_semantic_layer_commit_authoring_batch with the complete typed changes:
{ "semanticLayerId": "11111111-1111-4111-8111-111111111111", "planId": "22222222-2222-4222-8222-222222222222", "expectedRevision": 42, "items": [ { "id": "totalOrderAmount", "entity": "measure", "preparedTarget": null, "change": { "operation": "create", "name": "Total Order Amount", "definition": { "inputs": [ { "id": "orderAmount", "kind": "aggregate", "field": { "view": "Orders", "field": "Order Amount" }, "aggregation": "SUM" } ] } } }, { "id": "orderDate", "entity": "dimension", "preparedTarget": null, "change": { "operation": "create", "name": "Order Date", "definition": { "kind": "field", "field": { "view": "Orders", "field": "Order Date" } } } } ]}Copy planId, the revision, every preparedTarget, and all resolved views, fields, and
aggregations from the plan. Keep the item and input IDs consistent. If the plan finds an exact
existing use case, reuse it instead of creating a duplicate. The batch is all-or-nothing: if one
item fails compilation or validation, nothing is written. A successful commit creates at most one
draft revision and returns a receipt for every item.
Author views and fields from physical sources
Section titled “Author views and fields from physical sources”Use astrato_semantic_layer_prepare_view_change and astrato_semantic_layer_apply_view_change for table, SQL, and inline view creation;
view copy, rename, metadata/source update, and deletion; and field creation, update, rename, and
deletion. One ordered apply call can change several views and fields and commits at most one draft
revision without publishing.
When physical-source discovery is required, call astrato_semantic_layer_search_source_catalog with ordinary business
terms, or pass all source concepts to prepare so it can resolve them together. Keep the exact
schema, table, column identifiers, and casing returned by the source catalog. Never invent a source
identifier when discovery is ambiguous or empty.
Source discovery returns a progressive informationSchemaUri. Reuse that URI in later searches
and in view prepare/apply calls so previously loaded branches are not fetched again. Broad searches
are bounded; continue with nextSchemaOffset when it is returned. The resource expires after ten
minutes without use, after which omitting the URI starts a fresh context.
Call astrato_semantic_layer_refresh_source_catalog only when you need current source metadata. With no branch it
refreshes everything already loaded. A schema refreshes its table list and loaded columns; a table
refreshes that table and its columns. Refresh keeps the same URI and does not discover unrelated
branches.
Catalog-backed table creation can select all fields, an explicit subset, or all except an explicit list. Deletes that would break a join, dimension, measure, or filter are rejected instead of cascading silently. Copy every resolved source and revision from prepare into apply.
Some datasource-native values need a lossless semantic representation. View preparation returns
nativeDataTypeOptions for the active datasource. Each option declares the exact
nativeDataType, semantic dataType, and sourceType combination accepted by the engine. Use only
those returned combinations and only on physical column fields; inline columns and expression
fields cannot use datasource-native types.
For Google BigQuery, the currently supported option is:
{ "nativeDataType": "INT64", "dataType": "string", "sourceType": "column"}This keeps values larger than JavaScript’s lossless number range as strings while queries continue
to use BigQuery INT64. Setting another semantic datatype removes the native override. Source
checks compare the stored native datatype with the raw datatype currently reported by the database;
an actual upstream change is reported rather than hidden by the override.
Other entity edits
Section titled “Other entity edits”Use an edit session for focused join, function, and description changes supported by
astrato_semantic_layer_apply_edit. Description-only changes use the batched update_descriptions operation.
- Begin a session. Its base revision pins the work to a known draft state.
- Apply one or more structured edits. They remain session-local.
- Preview the complete diff and validation result.
- Commit the session when the preview is valid. Roll it back only if you intend to discard every pending change.
Sessions expire after 30 minutes of inactivity by default and belong to the current tenant and identity. A commit may rebase non-overlapping concurrent changes. If the same entity changed elsewhere, re-read the layer and begin a fresh session.
Validate and repair upstream changes
Section titled “Validate and repair upstream changes”astrato_semantic_layer_validate checks the semantic document. To check for database drift, call
astrato_semantic_layer_check_source_schema with fresh: true. A fresh check requires write access and may create
one draft revision when the database confirms that a referenced object is missing or has been
restored. Datatype changes and likely renames based on casing or separators remain suggestions.
Review every suggested fix and apply it only after explicit confirmation. Pass the exact runId,
entityKey, and revision returned by the check to astrato_semantic_layer_apply_source_schema_fix. See
Semantic layer issues for the meaning of each issue origin.
Import a Snowflake semantic view
Section titled “Import a Snowflake semantic view”List the views available on the datasource, then preview the selected import. Review the conversion counts and diagnostics before continuing. Import only after the preview is confirmed, and copy its revision and source hash exactly. Import creates a draft revision and a named version, but does not publish the layer.
New imports preserve case-distinct physical columns and their references. For example, Snowflake
expressions "YEAR" and "Year" import as separate fields. Unquoted column identifiers use
Snowflake’s uppercase spelling. Semantic layers imported earlier are not updated automatically.
Average metrics written as AVG(...) retain the AVG aggregation in the imported measure.
The data connection’s normal aggregation restrictions still apply.
Snowflake filters (a fact or dimension labelled FILTER, and the standalone filters of a table)
import as Boolean dimensions in the Filters category. A filter fact is never turned into a
measure. A metric that conditions on a filter, such as SUM(IFF(discounted_line, revenue, 0)),
imports as a measure with that filter. Labels other than FILTER are still not imported and are
reported in the preview diagnostics.
The imported layer records which semantic view, and which element of it, each table, field, join, dimension, and measure came from. The Semantic Layer Editor uses this to report later changes to the view; see When an imported Snowflake semantic view changes.
Version, export, and publish
Section titled “Version, export, and publish”astrato_semantic_layer_create_versioncreates a named, durable version of the draft head at call time. It does not publish. The tool does not acceptexpectedRevision, and its result does not identify the revision that was captured.astrato_semantic_layer_export_ossieexports the exact current validated V3 draft as Apache Ossie YAML. It does not publish. Advanced measures are retained as clearly labelled comment-only engine SQL; see Apache Ossie export.astrato_semantic_layer_export_snowflake_semantic_viewwrites the SQL that creates a Snowflake-backed layer as a Snowflake semantic view, for you to run in Snowflake. It does not publish or run anything; see Create a Snowflake semantic view.astrato_semantic_layer_publishis a separate write. Call it only when publishing was explicitly requested, and pass the exact revision that was confirmed. After publishing, it rates the joins that publish added or changed with the engine’s join-quality check and returns them underjoinQuality. Tell the user about every join listed underneedsAttention, with its recommendation. The check never blocks or fails the publish. When it cannot run,notMeasuredsays why.
To rate joins before publishing, call astrato_semantic_layer_check_join_quality. It measures the
draft’s joins. Queries keep using the published joins until you publish, so the result lists under
publishedJoinsNotInDraft the published joins that the draft removes or changes. A join on a view
that is new or changed in the draft is measured on the draft when your workspace routes draft
analysis through an exit node. Otherwise it is listed under needsAttention with the status
awaitingPublish until you publish. The same applies to every join when the draft uses another
data connection.
When you create a version and publish in the same workflow, validate revision R, create the
version, then immediately re-read the summary. Publish with expectedRevision: R only if the
summary still reports R. If the revision changed, stop: the version may have captured a different
draft. The publish call’s expected-revision check protects against another edit after the re-read.
After any write, re-read the summary before relying on the current revision. Another editor may have committed after the tool response.
Failure handling
Section titled “Failure handling”- Ambiguous match: review the alternatives and resolve the ambiguity before continuing. Do not guess.
- Stale revision: re-read the layer, then prepare or plan the change again.
- Validation failure: report the affected entity and diagnostics; do not publish or retry with invented data.
- Typed-workflow no-op: report that the layer was already in the requested state. Atomic and batch tools do not create a revision for a no-op.
- Missing scope or unavailable MCP endpoint: report the access needed. Do not fall back to undocumented HTTP calls or raw V3 edits.