> For the complete documentation index, see [llms.txt](https://v2.dataos.info/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://v2.dataos.info/references/dataos-resources/vulcan/core-concepts/plan.md).

# Plan

`vulcan plan` is the deployment step for project changes. Use it when you change models, semantics, metadata, tests, checks, or configuration. Vulcan compares your local project with the applied state, classifies the impact, computes what must be built, and shows the plan before anything is applied.

```bash
vulcan plan
```

`vulcan plan` does not just run data. It reviews what changed, classifies the impact, and decides what must be built before the next version is exposed. When you only need to process new data on an already-applied shape, use `vulcan run` instead (see [Observability](/references/dataos-resources/vulcan/observability.md)).

## What plan does

When you run `vulcan plan`, Vulcan:

1. Loads your project files and configuration.
2. Parses models, macros, semantics, tests, checks, and hooks, and builds the dependency graph (DAG).
3. Runs validation, tests, and the linter when enabled.
4. Fingerprints each model and reads the applied state.
5. Builds a context diff and classifies added, removed, direct, indirect, and metadata-only changes.
6. Computes the intervals that need backfill or restatement.
7. Shows the plan summary before anything is applied.

If you apply the plan, Vulcan updates the target state using the selected deployment mode. Without `--auto-apply`, Vulcan shows the plan and asks for confirmation.

## Two plan modes

The biggest difference between plans is whether your project uses a virtual layer, controlled by the `vde` key in `config.yaml`. Both modes parse the project, fingerprint snapshots, compare with applied state, classify changes, and compute intervals. They differ at promotion time.

| Mode                                             | Config                 | What promotion looks like                                                                        |
| ------------------------------------------------ | ---------------------- | ------------------------------------------------------------------------------------------------ |
| Without a virtual layer (direct materialization) | `vde: false` (default) | Models are written using their original, unversioned names.                                      |
| With a virtual layer                             | `vde: true`            | Consumer-facing names stay stable while Vulcan manages versioned physical snapshots behind them. |

{% hint style="info" %}
`vde: true` is rejected for `spark` and `trino` gateways. Use direct materialization there.
{% endhint %}

{% hint style="info" %}
Internally, `vde: false` maps to `virtual_environment_mode: DEV_ONLY`. In this mode, Vulcan does not promote changes through a virtual layer — environments are independent and isolated.
{% endhint %}

## Snapshot fingerprints

Vulcan does not decide a model changed by looking at the filename. It fingerprints the loaded node. A fingerprint has multiple parts:

* **Data hash:** the model logic that can affect output data.
* **Metadata hash:** metadata that can change catalog or governance behavior without changing rows.
* **Parent data hash:** upstream data-producing versions.

This is why a downstream model can be "modified" even when its own file was not edited: a parent fingerprint changed, so the downstream snapshot is indirectly affected. Plans are always DAG-aware.

## Change classification

The context diff compares local snapshots with snapshots in applied state and classifies each one:

* **Added:** exists locally but not in applied state. Vulcan creates a snapshot and, for materialized models, computes intervals to build. Added models are categorized as breaking for planning purposes because they introduce a new snapshot that must be built before applied state can reference it. In practice, adding a model usually does not break existing consumers unless downstream models or semantic surfaces are affected.
* **Removed:** exists in applied state but not locally. Removal is usually breaking if consumers, downstream models, or semantics reference the model. Check dependencies, semantic models and metrics, dashboards and API consumers, and policies before removing one. Plan does not immediately delete the removed physical snapshots; cleanup is handled later by retention and janitor behavior.
* **Directly modified:** the model's own definition changed (SQL or Python logic, kind, columns, incremental config, or metadata).
* **Indirectly modified:** an upstream dependency changed, so the parent data hash flows into the downstream snapshot. The plan separates direct edits from indirect impact so you can see the blast radius.
* **Metadata updated:** the model changed in a way that does not require recomputing data, such as a description, tags, terms, or owner. No backfill is required only because metadata changed, but the change still reaches catalogs, APIs, lineage, and documentation.

Changes are further categorized as breaking, non-breaking, indirect breaking, or indirect non-breaking:

* **Breaking** means existing downstream outputs or consumers may no longer be compatible: removing or renaming a column, changing a type or grain, or changing joins, filters, or aggregations that alter existing values.
* **Non-breaking** means the directly changed model needs a new version, but downstream models do not need a rebuild: adding an unused column, or a compatible addition downstream does not consume.
* **Indirect breaking / indirect non-breaking** describe how far that impact travels downstream.

A plan can have changes even when no model SQL changed. The context diff detects four additional categories that trigger a rebuild without any SQL edit:

* **Requirement changes** — Python dependency set changed.
* **State statement changes** — `before_all`, `after_all`, data product identity fields, or Python payload changed.
* **Virtual-layer config changes** — `gateway_managed_virtual_layer` setting changed.
* **New or unfinalized state** — model is new or its previous plan was not finalized.

## Forward-only changes

Forward-only means a change applies from a point in time forward without rewriting historical intervals. Use it when historical recomputation is too expensive, the business accepts that old intervals keep their previous logic, or a fix should affect future runs only.

```bash
vulcan plan --forward-only --effective-from 2026-06-01
```

When the virtual layer is disabled (`vde: false`), Vulcan forces plans into forward-only mode internally, so historical rewrites are not treated like reusable snapshot promotions. With a virtual layer, you can choose forward-only per plan with `--forward-only`, and `--effective-from` sets when the new logic starts. Destructive and additive schema changes may require explicit allowance depending on model settings.

## Backfill

Backfill means running model logic to populate missing or required intervals for a snapshot. It is not "run the whole pipeline." A backfill is scoped by snapshot version, model kind, date interval, DAG dependencies, and the flags you pass.

For an incremental model, a backfill is a set of intervals:

```
analytics.orders_daily:
  [2026-05-01, 2026-05-02)
  [2026-05-02, 2026-05-03)
```

For a full model, a backfill is usually a full refresh. Backfill is required for a new materialized snapshot, for new snapshot versions from a breaking change, or after a restatement clears intervals. It is not required for metadata-only changes, for intervals that already exist and can be reused, or when you pass `--skip-backfill` (do not run compute) or `--empty-backfill` (mark intervals processed without materializing).

## Restatement

Restatement asks Vulcan to recompute already-processed intervals. Use it when source data changed historically, a bug affected an old interval, or you need a controlled correction window. Keep restatements bounded and explicit.

```bash
vulcan plan --restate-model analytics.orders --start 2026-05-01 --end 2026-05-07
```

With a virtual layer, a restatement can also clear intervals from other versions of the model when appropriate, so interval state stays consistent across promoted snapshots.

## How the virtual layer changes apply

The virtual layer separates three concerns that are usually tangled together: the code version, the physical data table for one snapshot version, and the stable consumer-facing name. Consumers query a stable name while Vulcan controls which physical version that name points to.

```
Consumer-facing name:
  analytics.orders

Physical snapshot tables:
  sqlmesh__analytics.analytics__orders__123abc
  sqlmesh__analytics.analytics__orders__789def

Virtual layer:
  analytics.orders -> sqlmesh__analytics.analytics__orders__789def
```

{% hint style="info" %}
Physical snapshot tables follow the naming pattern `sqlmesh__<schema>.<schema>__<model>__<fingerprint>`. For example, `sqlmesh__analytics.analytics__orders__123abc` and `sqlmesh__analytics.analytics__orders__789def` are two snapshots for the same model at different versions.
{% endhint %}

With a virtual layer, applying a plan has two phases:

1. **Physical phase.** Create or reuse physical snapshot tables and fill missing intervals.
2. **Virtual phase.** Point consumer-facing views at the selected snapshot tables.

If all required data already exists, the plan becomes mostly a fast virtual update, because Vulcan only changes view definitions or state pointers. Consumers keep reading the old snapshot while a new one is built, and if apply fails before the virtual layer is updated, consumers continue using the previous version. Vulcan can also mix reuse and rebuild: a changed upstream snapshot is rebuilt, an unaffected downstream snapshot is reused, and the consumer-facing view points to the correct combination, so the whole DAG does not rebuild for every edit.

Without a virtual layer, the model's original object name is the object being updated. There is no swap that keeps consumers on the old version while a new snapshot is prepared, and plans are forward-only. This mode is simpler and fits engines that cannot support virtual-layer promotion (such as Spark or Trino), but it gives up the strongest isolation and rollback properties.

## What plan apply does

When a plan is applied, Vulcan:

1. Runs `before_all` hooks when configured.
2. Creates missing schemas and physical tables.
3. Backfills missing intervals for selected snapshots.
4. Runs assertions and DQ checks for evaluated models where configured.
5. Updates state with the promoted snapshots, and updates virtual-layer views when `vde: true`.
6. Runs `after_all` hooks when configured.
7. Records plan activity, backfill details, errors, and follow-on run activity.

## Selecting models

In large projects, a single change can affect many downstream models. Use `--select-model` to narrow which directly modified models a plan includes; Vulcan figures out the indirect ones. Selection works on the dependency graph, so selecting a model pulls in everything downstream of it.

```bash
# Select a single model
vulcan plan dev --select-model "sales.daily_sales"

# Wildcards, tags, and git changes
vulcan plan dev --select-model "raw.*"
vulcan plan dev --select-model "tag:seed"
vulcan plan dev --select-model "git:feature"

# Upstream (+model), downstream (model+), or both (+model+)
vulcan plan dev --select-model "+sales.daily_sales+"
```

Limit which selected models actually backfill with `--backfill-model` (development environments only); upstream dependencies are always backfilled when required. See the [model selection guide](/references/dataos-resources/vulcan/incremental-by-time/model-selection.md) for the full selector syntax and examples.

## Useful commands

```bash
vulcan plan                                   # plan the default state
vulcan plan --start 2026-05-01 --end 2026-05-31   # bound the interval
vulcan plan --no-prompts --auto-apply         # apply without prompts
vulcan plan --skip-tests                      # skip tests (accept the risk)
vulcan plan --skip-linter                     # skip the linter (accept the risk)
vulcan plan --skip-backfill                   # do not run compute
vulcan plan --empty-backfill                  # record intervals without computing
vulcan plan --forward-only --effective-from 2026-06-01
vulcan plan --restate-model analytics.orders --start 2026-05-01 --end 2026-05-07
vulcan plan --explain                         # explain the plan without applying
```

{% hint style="info" %}
`--explain` switches the focus from application to explanation — it shows the full plan diff, change categories, and affected intervals, but does not prompt for confirmation and does not apply. Use it for PR reviews or audits.
{% endhint %}

## Common misunderstandings

* **A plan without a virtual layer is not useful.** It still detects changes, computes impact, shows backfills, and records activity.
* **Backfill always rebuilds all history.** Backfill is scoped by model, interval, and flags.
* **Indirectly modified means the file changed.** It means an upstream fingerprint changed and this node may be affected through the DAG.
* **Metadata-only changes are not deployed.** They are deployed to state and metadata surfaces; they just do not require data recomputation.

## Related docs

* [State](/references/dataos-resources/vulcan/core-concepts/state.md): what Vulcan stores to know what has already been applied.
* [Observability](/references/dataos-resources/vulcan/observability.md): `vulcan run` and how scheduled refreshes process intervals.
* [Model selection guide](/references/dataos-resources/vulcan/incremental-by-time/model-selection.md): full selector and backfill syntax.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://v2.dataos.info/references/dataos-resources/vulcan/core-concepts/plan.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
