xmatix migrate is the CI-friendly two-step for promoting a repository's checked-in state onto a target tenant: plan produces a reviewable diff manifest of exactly what a push would create and overwrite, and apply performs the push. The same pair, pointed at an unchanged repository, doubles as a drift detector — a plan that is not all-unchanged means the tenant was modified outside the repo.
xmatix migrate plan # → xmatix/.snapshot/migrate-plan.json
xmatix migrate plan --keep-server-snapshot
jq '.counts' xmatix/.snapshot/migrate-plan.json
xmatix migrate apply --dry-run
xmatix migrate apply
How migrate plan works
- Bootstraps a temporary workspace in the OS temp directory, pointed at the same tenant as your repo (it copies your config).
- Runs a full
snapshot exportinto that temporary tree — a fresh, complete picture of the live tenant. - Walks each canonical folder —
metadata,automation/scripts,automation/rules,automation/workflows,analytics/dashboards,analytics/reports— and content-hashes (SHA-256) every file in both trees. - Classifies each file:
| Status | Meaning | What apply does |
|---|---|---|
added | Present locally, missing on the server | Creates it |
modified | Content hash differs | Overwrites the server |
removed | Present on the server, missing locally | Nothing — never deletes |
unchanged | Identical | Nothing |
- Writes the structured manifest to
xmatix/.snapshot/migrate-plan.json— generation time, per-status counts, and every entry with its path and status — sized for both human review in a pull request and machine assertion in a pipeline.
--keep-server-snapshot retains the live export under xmatix/.snapshot/server/ so reviewers can open the server's actual files next to the repo's, and --scope passes a metadata scope through to the underlying export (default TenantOwned). --out redirects the manifest.
migrate apply
migrate apply is currently an alias for snapshot import — it pushes every artifact file in the canonical folders, in dependency order, and passes through --skip and --dry-run. Always run --dry-run first in a pipeline: it prints what would be pushed without touching the tenant, which catches a mis-scoped workspace before it matters.
Removed files are never deleted
Neither apply nor snapshot import deletes anything on the server. A file in the plan's removed set means the server has an artifact your repo does not — perhaps deliberately deleted from the repo, perhaps created ad hoc in the product. Deleting server artifacts is a manual, in-product decision: review the removed section and act on each entry knowingly. This asymmetry is deliberate — an automated pipeline should never be able to destroy configuration because of a missing file.
Common questions
How do I gate a pipeline on "no drift"?
Assert on the plan's counts. After xmatix migrate plan:
jq -e '.counts.modified == 0 and .counts.removed == 0' \
xmatix/.snapshot/migrate-plan.json
fails the job when the live tenant has diverged from the repository. The CI recipes page has the full workflow.
Does apply push only what the plan flagged?
Not yet — apply currently pushes every file in the canonical folders (unchanged pushes are harmless upserts), while the plan is the review artifact. Consuming the plan so apply sends only added and modified files is a planned refinement; until then, use --skip to exclude whole kinds when that matters.
Can I promote from one tenant to another with this?
Yes — that is the primary use. The repository holds the reviewed state exported from the source tenant; a workspace configured for the target tenant runs migrate plan (showing exactly how the target differs) and then migrate apply. Order matters across kinds, and apply already pushes metadata before the automation and analytics that depend on it.
