The CLI's verbs compose into three pipeline patterns: a drift gate that fails a build when the live tenant no longer matches the repository, a promotion job that applies a tagged repository state to a tenant, and a release-time snapshot that proves the repo captured everything before shipping. All three start with xmatix doctor so auth, config and connectivity problems fail fast with a named cause.
One constraint shapes everything on this page: the CLI signs in with a device-code flow only — there is no service-principal sign-in yet (see Authentication). Pipelines therefore run on a persistent, access-controlled runner where xmatix login was completed once; the encrypted token cache then serves silent sign-ins across jobs. On ephemeral runners the recipes below will fail at doctor's token check until non-interactive sign-in ships.
Drift gate
Fails the build if any artifact changed on the live tenant since the last commit — run it on pull requests and on a schedule:
name: drift-gate
on: [pull_request, schedule]
jobs:
drift:
runs-on: [self-hosted, xmatix-runner]
steps:
- uses: actions/checkout@v4
- run: |
xmatix doctor
xmatix migrate plan
jq -e '.counts.modified == 0 and .counts.removed == 0' \
xmatix/.snapshot/migrate-plan.json
migrate plan exports a fresh snapshot from the tenant and hash-compares it against the checkout (how the plan works); the jq assertion turns its counts into a pass/fail. A failing gate means someone changed the tenant in the product without exporting — the fix is a fresh snapshot export committed through review, not a forced apply.
Promote a release
Applies the repository state to the tenant when a release tag is pushed:
name: promote
on:
push:
tags: ['v*']
jobs:
promote:
runs-on: [self-hosted, xmatix-runner]
steps:
- uses: actions/checkout@v4
- run: xmatix doctor
- run: xmatix migrate apply --dry-run
- run: xmatix migrate apply
The --dry-run before the real apply is cheap insurance: it prints exactly what will be pushed, so a mis-scoped checkout or a missing folder aborts before anything is written. Remember that apply never deletes server artifacts — removals stay manual by design.
Release-time snapshot
For any CI system — three commands and a git check prove the repository captured the tenant as shipped:
xmatix doctor
xmatix snapshot export
git diff --exit-code xmatix/
A non-zero exit from git diff means the export produced changes the repo did not have — the release is drifting from its own record. Archive the diff as a build artifact when this gate fails; it is the exact list of what was changed in-product.
Local pre-commit hook
A lightweight local guard that keeps metadata honest before it ever reaches CI — in .git/hooks/pre-commit:
#!/usr/bin/env bash
set -e
xmatix doctor
git diff --cached --name-only | grep -q '^xmatix/' \
&& xmatix metadata diff xmatix/metadata/tenant-owned.json
When a commit touches the artifact tree, metadata diff compares the committed seed-pack against a live export, so a stale metadata file gets caught at commit time rather than at review.
Common questions
Which user should the pipeline sign in as?
A dedicated integration account with exactly the customization capabilities the pipeline needs — the CLI acts with the signed-in user's permissions, so the account's grants are the pipeline's blast radius. Sign that account in once on the runner, and treat the runner's home directory (where the encrypted token cache lives) with the same care as any credential store.
How does the pipeline know which tenant it targets?
From the checked-out .xmatix/config.json — the workspace is the targeting. This is worth making explicit in the repo structure: one repository (or one directory with its own workspace) per tenant, so a pipeline can never apply the right files to the wrong tenant by picking up an unexpected config.
Can the drift gate and promotion target different tenants?
Yes, with one workspace each — for example, drift-gating a production tenant while promoting to a staging tenant first. Each job checks out (or cds into) the workspace for its tenant; nothing else changes, because every command resolves tenant, API and identity from the workspace it runs in.
