Atlas-compatible commands
The Atlas-compatible reference records what each ptah-compat <command> does,
where it differs from Atlas, and which inputs fail explicitly. Usage, flags,
and worked examples live on
Atlas migrate commands and
Atlas schema commands; the surfaces and
translation model are on the
Atlas compatibility overview. Native verbs are on
Native commands.
The Atlas-compatible commands are hosted by the separate ptah-compat binary,
a drop-in replacement for scripts that need Atlas-style root commands; the main
ptah binary has no Atlas command paths. The invocations on this page are
written as ptah-compat <command> ..., the name the binary ships under. Each
verb section names its native ptah twin.
Every Atlas-compatible command
Section titled “Every Atlas-compatible command”The index below is generated from the ptah-compat command tree, so it names
every command path the binary answers to and no path it does not. Rows are
sorted, which keeps a namespace’s verbs together, and the What it does
cell is the command’s own one-line description. What each verb does in full,
and where it differs from Atlas, is the section for that verb further down;
version, license and completion are under
Utility commands.
A row marked group is a namespace rather than an operation. ptah-compat help and the four ptah-compat completion shells have rows because the
shipped binary answers to them; __complete and __completeNoDesc ship too,
and no walk of the tree reaches them, so they are named here rather than
listed.
| Command | What it does | Notes |
|---|---|---|
ptah-compat completion |
Generate the autocompletion script for the specified shell | group |
ptah-compat completion bash |
Generate the autocompletion script for bash | — |
ptah-compat completion fish |
Generate the autocompletion script for fish | — |
ptah-compat completion powershell |
Generate the autocompletion script for powershell | — |
ptah-compat completion zsh |
Generate the autocompletion script for zsh | — |
ptah-compat help |
Help about any command | — |
ptah-compat license |
Print license information | — |
ptah-compat migrate |
Atlas migrate commands | group |
ptah-compat migrate apply |
Apply pending migrations | — |
ptah-compat migrate checkpoint |
Squash migration history into a cumulative-schema checkpoint | — |
ptah-compat migrate diff |
Compute migration diff against a desired schema | — |
ptah-compat migrate down |
Roll back migrations | — |
ptah-compat migrate edit |
Edit a migration file and update the directory checksum | — |
ptah-compat migrate hash |
Write or update the migration directory checksum | — |
ptah-compat migrate import |
Import migrations from another tool | — |
ptah-compat migrate lint |
Lint migration files | — |
ptah-compat migrate ls |
List the migration files in the directory | — |
ptah-compat migrate new |
Create a new migration file | — |
ptah-compat migrate push |
Push migration directory to a remote registry | — |
ptah-compat migrate rebase |
Move a migration to the end of history and update the directory checksum | — |
ptah-compat migrate rm |
Remove a migration file and update the directory checksum | — |
ptah-compat migrate set |
Set migration revision state | — |
ptah-compat migrate show |
Print the contents of one or more migration files | — |
ptah-compat migrate status |
Show migration status | — |
ptah-compat migrate test |
Run declarative migration tests against a dev database | — |
ptah-compat migrate validate |
Validate migration directory integrity | — |
ptah-compat schema |
Atlas schema commands | group |
ptah-compat schema apply |
Apply a desired schema to a database | — |
ptah-compat schema clean |
Clean database schema objects | — |
ptah-compat schema diff |
Diff desired schema against another schema | — |
ptah-compat schema fmt |
Format schema files | — |
ptah-compat schema inspect |
Inspect a database schema | — |
ptah-compat schema plan |
Plan a declarative migration for a schema transition | group |
ptah-compat schema plan approve |
Approve a plan in a remote registry | — |
ptah-compat schema plan lint |
Run analysis (migration linting) on a plan file | — |
ptah-compat schema plan list |
List plans in a remote registry | — |
ptah-compat schema plan new |
Create a new plan file for the schema transition | — |
ptah-compat schema plan pull |
Pull a plan from a remote registry | — |
ptah-compat schema plan push |
Push a plan to a remote registry | — |
ptah-compat schema plan rm |
Remove a plan from a remote registry | — |
ptah-compat schema plan test |
Run schema plan tests | — |
ptah-compat schema plan validate |
Validate a plan file against the schema transition | — |
ptah-compat schema push |
Push schema state to a remote registry | — |
ptah-compat schema stats |
Schema statistics | group |
ptah-compat schema stats inspect |
Count the objects in a live schema and emit them as OpenMetrics | — |
ptah-compat schema test |
Run declarative schema tests against a dev database | — |
ptah-compat schema validate |
Report structural problems in a desired schema without a database | — |
ptah-compat script |
Run a declared data operation | group |
ptah-compat script exec |
Run a declared exec script | — |
ptah-compat script loop |
Run a declared loop script | — |
ptah-compat script query |
Run a declared query script | — |
ptah-compat version |
Print Ptah version information | — |
Utility commands
Section titled “Utility commands”ptah-compat version prints Ptah build information.
ptah-compat license prints Ptah’s MIT license and the license-clean Atlas
compatibility notice.
ptah-compat completion <shell> generates completion output for the
Atlas-compatible command tree under the invoked executable name, so an
installation renamed for a drop-in completes under the name it was invoked as.
The Atlas compatibility overview documents that
rename.
Commands strict mode removes
Section titled “Commands strict mode removes”PTAH_ATLAS_STRICT_COMPAT=1 selects a separate Atlas Community Edition policy,
which constructs a smaller command tree before dispatch. The
Atlas compatibility overview states what that selector
is for and what else it narrows. This section is the command-level answer: 28
of the 52 paths above leave the surface, and they do not all answer the same
way.
- refused — 12 paths stay registered and hidden, so each one’s own gate answers and the message names the path that was invoked.
- refused by its group — 10 paths sit below a group that is itself refused. The group’s gate answers and names the group, never the path invoked, so a script searching the message for its own verb finds nothing.
- absorbed by the group — 2 paths are unregistered below a group cobra will run, so that group runs in their place.
- unknown command — 4 paths are unregistered below the root, which cobra cannot run, so it reports the first segment it could not resolve.
Two paths exit 0, and they are the ones worth knowing about.
ptah-compat migrate ls and ptah-compat migrate show are not registered
under PTAH_ATLAS_STRICT_COMPAT=1, so the migrate group runs in their place:
it writes its own help to standard output, writes nothing to standard error,
and exits 0. A caller testing only the exit code cannot tell that from the
verb having run and found nothing. Test for the output the verb produces, or
leave the variable unset.
A real invocation usually carries a flag, and that one fails:
$ PTAH_ATLAS_STRICT_COMPAT=1 ptah-compat migrate ls --dir file://migrationsError: unknown flag: --dirThe message is about a flag, for a verb that is not there, and it exits 1.
The fourth class names only the first segment it could not resolve, so the sub-verb never appears:
$ PTAH_ATLAS_STRICT_COMPAT=1 ptah-compat script queryError: unknown command "script" for "atlas"The name in that message is the one the binary reports itself under rather than the file name it was installed as.
| Command | Under PTAH_ATLAS_STRICT_COMPAT=1 |
Exit | Stream | The answer names |
|---|---|---|---|---|
ptah-compat migrate checkpoint |
refused | 1 |
stderr | ptah-compat migrate checkpoint |
ptah-compat migrate down |
refused | 1 |
stderr | ptah-compat migrate down |
ptah-compat migrate edit |
refused | 1 |
stderr | ptah-compat migrate edit |
ptah-compat migrate ls |
absorbed by the group | 0 |
stdout | ptah-compat migrate |
ptah-compat migrate push |
refused | 1 |
stderr | ptah-compat migrate push |
ptah-compat migrate rebase |
refused | 1 |
stderr | ptah-compat migrate rebase |
ptah-compat migrate rm |
refused | 1 |
stderr | ptah-compat migrate rm |
ptah-compat migrate show |
absorbed by the group | 0 |
stdout | ptah-compat migrate |
ptah-compat migrate test |
refused | 1 |
stderr | ptah-compat migrate test |
ptah-compat schema plan |
refused | 1 |
stderr | ptah-compat schema plan |
ptah-compat schema plan approve |
refused by its group | 1 |
stderr | ptah-compat schema plan |
ptah-compat schema plan lint |
refused by its group | 1 |
stderr | ptah-compat schema plan |
ptah-compat schema plan list |
refused by its group | 1 |
stderr | ptah-compat schema plan |
ptah-compat schema plan new |
refused by its group | 1 |
stderr | ptah-compat schema plan |
ptah-compat schema plan pull |
refused by its group | 1 |
stderr | ptah-compat schema plan |
ptah-compat schema plan push |
refused by its group | 1 |
stderr | ptah-compat schema plan |
ptah-compat schema plan rm |
refused by its group | 1 |
stderr | ptah-compat schema plan |
ptah-compat schema plan test |
refused by its group | 1 |
stderr | ptah-compat schema plan |
ptah-compat schema plan validate |
refused by its group | 1 |
stderr | ptah-compat schema plan |
ptah-compat schema push |
refused | 1 |
stderr | ptah-compat schema push |
ptah-compat schema stats |
refused | 1 |
stderr | ptah-compat schema stats |
ptah-compat schema stats inspect |
refused by its group | 1 |
stderr | ptah-compat schema stats |
ptah-compat schema test |
refused | 1 |
stderr | ptah-compat schema test |
ptah-compat schema validate |
refused | 1 |
stderr | ptah-compat schema validate |
ptah-compat script |
unknown command | 1 |
stderr | ptah-compat script |
ptah-compat script exec |
unknown command | 1 |
stderr | ptah-compat script |
ptah-compat script loop |
unknown command | 1 |
stderr | ptah-compat script |
ptah-compat script query |
unknown command | 1 |
stderr | ptah-compat script |
Migrate commands
Section titled “Migrate commands”ptah-compat migrate apply
Section titled “ptah-compat migrate apply”Applies Atlas-format migration directories with Atlas-compatible apply flags
and Atlas revision bookkeeping by default. With --env, reads env.url,
migration, and format.migrate.apply from atlas.hcl.
Executes every Atlas OSS directory format selected by migration.format or a
?format= directory URL query; non-atlas formats are converted in memory to
up-only migrations.
Honors the -- atlas:checkpoint file directive: a fresh database applies
only the latest checkpoint plus post-checkpoint migrations, and a database
that already applied pre-checkpoint history silently skips the checkpoint,
matching measured Atlas behavior.
Fails before the target database is opened: unknown formats, two Flyway
files with the same exact revision identity, goose/dbmate files missing their
up directive, colliding versions, an Atlas directory that fails atlas.sum verification, and
an Atlas directory that carries no atlas.sum at all while holding at least one
top-level .sql file — both checksum refusals are byte-identical to
ptah-compat migrate validate and nothing is applied. A directory with no
top-level .sql file reports No migration files to execute and exits 0.
Direct Flyway apply records each source version token byte for byte, including
dotted, dot-prefixed, padded, nonnumeric, baseline, token-ending-R, and empty
repeatable tokens. --baseline and the extended --to-version address those
exact tokens; the numeric projection controls execution order only. One
repeatable migration can own the empty token and remains settled after its body
is edited and rehashed. Two repeatables collide and are refused before the
target opens. Applied opaque history remains readable after its source file is
removed, without recreating pending work; its exact token still protects
source order. The known .atlas_cloud_identifier bookkeeping row remains
metadata rather than becoming a migration.
In --format '{{ json . }}' output, an exact empty current or target identity
is emitted as an explicit empty Current or Target member. Pending and
applied file records likewise keep an explicit empty Version. A genuinely
absent current or target state omits that member.
The revision table must distinguish every exact source token under its
configured version collation. Ptah-created MySQL and MariaDB tables use
utf8mb4_bin, and SQL Server tables use Latin1_General_100_BIN2. An existing
table whose collation aliases two covered tokens, such as A and a, is
refused before migration SQL runs rather than applying one body and failing on
the second primary-key write.
Atlas CE accepts a Ptah-written exact-token history, and Ptah continues a CE-written one. Each side records a different per-revision checksum:
- CE stores the source directory’s
atlas.sumh1. - Ptah stores the digest of the migration body.
Ptah accepts either when verifying a row, so an existing CE-managed Flyway
database needs no migrate set or revision-table rewrite before Ptah applies
the next migration. Ptah keeps writing its own encoding for rows it adds,
because an atlas.sum h1 chains over every preceding file and a history keyed
on it stops verifying whenever an unrelated migration is inserted ahead of it.
A file edited after it was applied is still refused, since neither checksum
matches bytes that changed.
A
same-token V2/B2 row is also refused when CE’s ordinary applied type cannot
prove which byte-identical source ran; Ptah’s own executed-baseline marker still
renders as applied and does not create --baseline boundary semantics. A
successful explicit --baseline 2 that selects B2 writes the ordinary
baseline type plus a durable source-baseline marker, settling that exact
identity on this and later runs. Baselining V2 does not authorize a B2
introduced later with the same token. A Ptah migrate set of an executed
baseline renders as manually set while retaining a separate settled-baseline
marker; CE can still read the row.
The scan is top-level-only, matching what atlas.sum covers and what Atlas CE
reads. A .sql file in a subdirectory, or a top-level .SQL, is not a
migration and is not executed; each one is named on stderr as declined, which
Atlas CE does not do — see #976.
A leading -- atlas:txmode file or -- atlas:txmode none header overrides
global file or none for that migration. File-level all, unknown values,
duplicates, and explicit file modes under global all fail before the affected
body or revision row changes. The directive belongs to the initial line-comment
header; a blank line after the header is accepted but not required. Txtar
migration.sql and down.sql sections carry independent modes; a mode before
the -- atlas:txtar marker is rejected as an unsafe archive classification.
Directories in an external tool’s format are gated on the atlas.sum the source
directory carries, verified before the source layout is parsed and before the
database is opened. The covered file set is Atlas’s for that layout, so a
golang-migrate down file and a Flyway undo file are not covered, and a layout
that carries no atlas.sum and whose covered set is empty is not a checksum
error. What executes is what the verified checksum covers, for every layout.
Rejected on this verb, matching Atlas OSS: --dir-format.
--to-version bounds the run at a migration version: every pending migration up
to and including it runs, and nothing above it does. The bound is enforced where
the apply plan is built, inside the migration lock, so a concurrent writer
cannot turn it into a different set of migrations; a version the directory does
not carry is refused rather than rounded to a neighbor, and the bound cannot be
combined with the amount argument, because the two select different prefixes and
neither outranks the other. The pinned community binary does not register the
flag — Atlas’s published CLI reference does, which is what makes this a
Pro-surface addition rather than a CE parity row.
ptah-compat migrate apply --url "$DB" --dir file://migrations --to-version 20240101000002--lock-name replaces the name of the session advisory lock that
serializes migration runs (ptah_migrate by default). Runs serialize only
against other runs naming the same lock. A lock another process holds makes the
run wait, bounded by --lock-timeout; an elapsed timeout fails the run before
any migration executes. An empty value is refused rather than silently falling
back to the default. On a dialect with no advisory-lock semantics the run
prints a stderr note naming the lock it did not acquire.
--skip-lock takes no lock at all, so a lock another process holds is
ignored rather than waited on and concurrent runs can interleave. It cannot be
combined with --lock-name.
Pre-migration checks — -- +ptah check directives and Atlas txtar
checks.sql / checks/*.sql sections — are enforced here as they are natively.
Atlas registers no --skip-checks on migrate apply (measured: CE v1.2.0
answers with unknown flag, and Atlas’s own help surface registers it only
on migrate down), so the emergency bypass is the PTAH_SKIP_CHECKS
environment variable rather than a flag this surface must not grow:
PTAH_SKIP_CHECKS=1 ptah-compat migrate apply --url "$DB" --dir file://migrationsIt reads like every other boolean PTAH_* variable — unset enforces the checks,
a valid boolean is honored, and anything else, an exported empty value included,
fails the run before a migration is applied. It warns on stderr while active and
bypasses checks only: atlas.sum verification and revision bookkeeping are
unaffected. See
Pre-migration checks and
Boolean environment variables.
Native twin: ptah migrations up.
ptah-compat migrate status
Section titled “ptah-compat migrate status”Reports Atlas-format migration status with Atlas revision-table metadata and
Atlas-format migration directories by default. Supports --dir-format atlas,
--revisions-schema, and Atlas Go-template --format output over .Env,
.Available, .Applied, .Pending, .Current, .Next, .Status, and — on
a half-applied migration — .Count, .Total, .SQL, and .Error.
For a direct Flyway directory, available, applied, pending, current, and next
versions use the exact source tokens. Numeric keys remain internal ordering
values and never replace those identities in the report. Current follows the
pinned binary’s textual maximum over applied source tokens; numeric high-water
continues to govern execution internally. In JSON templates, a present empty
current identity and each empty file or revision identity remain explicit
empty members rather than disappearing through omitempty.
The default (no --format) report mirrors the Atlas shape, because this is the
verb pipelines parse:
Migration Status: PENDING -- Current Version: No migration applied yet -- Next Version: 20260721120000 -- Executed Files: 0 -- Pending Files: 2Executed Files counts revision rows and Pending Files counts directory files
not yet recorded. A half-applied migration annotates the first three lines
((1 statements applied), (1 statements left), (last one partially)) and
adds a Last migration attempt had errors: block naming the failing statement.
Native twin: ptah migrations status, which keeps its
own block — only the compatibility surface is a contract with an existing
pipeline.
ptah-compat migrate hash
Section titled “ptah-compat migrate hash”Writes atlas.sum for the migration directory. --dir-format defaults to
atlas, so the compatibility path writes atlas.sum by default, and the
atlas layout forwards to ptah migrations hash. A successful compatibility
hash is silent, matching Atlas CE; inspect or commit the resulting atlas.sum
instead of relying on a progress message.
When migration.dir is a data.template_dir URL, the checksum belongs to the
immutable rendered view. The command remains silent and does not create an
atlas.sum beside the source templates.
ptah-compat migrate validate
Section titled “ptah-compat migrate validate”Silently verifies atlas.sum on success. Missing or mismatched checksum files
use Atlas-compatible exit-1 stdout/stderr diagnostics, and --dev-url cleans
the dev database and replays the migration directory to validate SQL
execution. Native ptah migrations validate keeps its own banner and exit
contract.
A data.template_dir URL is validated through its rendered snapshot; the
source templates are not modified.
ptah-compat migrate ls
Section titled “ptah-compat migrate ls”Lists the migration files the directory holds, oldest version first, one file
per line. -s/--short collapses each migration to its version, dropping the
description and the .sql suffix, and -l/--latest keeps only the newest
migration. No database is contacted and none of the SQL is executed.
The directory is verified against its atlas.sum before anything is listed, and
a directory carrying no atlas.sum is refused. An empty directory is not: there
is nothing to verify and nothing to list, so it exits 0 having printed nothing.
Native twin: ptah migrations ls, which defaults to
Ptah’s own layout, offers --dir-format, and runs the checksum gate only under
--verify-sum — it is a read-only verb, so it describes a directory that has
drifted instead of refusing to.
ptah-compat migrate show
Section titled “ptah-compat migrate show”Prints the SQL of each named migration, exactly as it sits on disk. A migration is named by version or by file name, the positional is repeatable, and the bodies are printed in the order named, separated by a blank line, with a migration named twice printed once. Every named migration is located before any of it is printed, so a run naming one migration that is not there prints nothing.
The same atlas.sum gate as ls applies, for the same reason.
Native twin: ptah migrations show, which takes the
version on a repeatable --version flag rather than a positional and adds
--direction to select which half of a reversible Ptah pair is printed.
Source directory layouts on the verbs that read a directory
Section titled “Source directory layouts on the verbs that read a directory”hash, validate, lint, status, and set read a migration directory
written by another tool. ls and show do not: they register no
--dir-format, because the verbs they mirror register none either, and they
read the directory as the Atlas layout. The layout is selected with either spelling Atlas
accepts, and hash and validate agree on the resulting atlas.sum:
ptah-compat migrate hash --dir "file://migrations?format=goose"ptah-compat migrate hash --dir file://migrations --dir-format gooseAccepted values are atlas (the default), golang-migrate, goose, flyway,
liquibase, and dbmate. migration.format in atlas.hcl selects the same
thing under --env. When the query and the flag disagree, the query wins: an
empty ?format= selects the atlas layout whatever the flag says.
Each layout covers a different set of source files, matching Atlas:
| Layout | Files the checksum covers |
|---|---|
atlas, goose, liquibase, dbmate |
every top-level *.sql, ordered by name |
golang-migrate |
every top-level *.up.sql. The down file of a pair is not covered, so editing it is invisible to validate |
flyway |
the whole tree: V and B files, then R repeatables last. U undo files are dropped, and everything at or below the highest baseline is squashed |
Format names are matched exactly, so GOOSE and " goose " are unknown
formats rather than goose. A directory with nothing for the layout to cover —
an empty directory, or a golang-migrate directory holding only a down file —
hashes to the empty-set checksum and validates clean.
An empty --dir-format, a query parameter other than format, and a repeated
format parameter are all accepted and read exactly as Atlas reads them: the
empty value and the unknown key select the atlas layout, and a repeated key
takes the first value.
format is the only query key that selects anything. A key that selected
nothing is named on standard error and the run continues, on each of the eight
verbs that accepts a --dir query — apply, diff, hash, lint, new,
set, status and validate:
note: atlas migrate apply --dir: ignoring migration directory URL query key"fromat". Only ?format= selects the directory layout. SetPTAH_STRICT_DIR_QUERY=1 to refuse an unrecognized key instead.The exit code and everything on standard output are unchanged, so a script
reading either sees exactly what Atlas produces. The note exists because
dropping the key is what Atlas does and saying nothing about it is not: a
misspelled ?fromat=goose selects no layout on either tool, so the directory is
read as the atlas layout while you believe it is being read as Goose. Set
PTAH_STRICT_DIR_QUERY=1 to make that a
refusal instead.
checkpoint, down, edit, rebase, rm and test register --dir too and
refuse any query on it — migration directory URL query parameters are not supported for this command — so neither the note nor the variable applies
there. The pinned community binary answers unknown flag: --dir on all six, so
this is stricter than a CLI with no contract on those verbs rather than a parity
gap.
Inputs that stay refused where Atlas CE exits 0, all of them loudly:
- a semicolon in the query, such as
?format=flyway;x=1, which Atlas drops whole and reads as the atlas layout; - a query on a
--dirthat a later--diroverrides; - a stray positional argument, including one after
--.
None of them can produce a wrong checksum. They are tracked in
#990; the query rules are shared
with migrate apply, so relaxing one widens what the integrity gate accepts.
migrate apply registers no --dir-format at all, matching Atlas. It gates a
directory read through ?format= over the same per-layout file set hash
writes, so what migrate hash writes is what migrate apply verifies.
migrate new and migrate diff both write the selected layout, gating the
directory over that layout’s covered file set first. migrate diff composes
each layout’s own files: a forward and a rollback file for golang-migrate
(.up.sql / .down.sql) and flyway (V… / U…), both halves under
directives in one file for goose and dbmate, and a changeset carrying
--rollback: lines for liquibase. atlas.sum is written over that layout’s
covered file set, so the community binary’s own migrate validate reads back
what Ptah wrote.
The generated SQL is Ptah’s renderer’s, on every layout including atlas — the
layout is what these follow, not the DDL text. On liquibase, Ptah writes ONE
changeset carrying the whole migration and all of its --rollback: lines, where
the community binary writes one changeset per statement; rolling the migration
back is exact either way, but Ptah does not offer per-statement rollback there,
because pairing each forward statement with a reverse statement would be a guess
about a reverse plan that is computed for the run as a whole.
The rollback half is planned against the state the migration starts from, not the state it produces, so a forward migration that DROPS a table rolls back into the CREATE TABLE that puts it back. That re-created table carries its own primary key and its single-column foreign keys, and the rollback does not repeat them; a rollback that did is refused by the server outright. A CHECK constraint is not in the table body at all, so it is restored by its own statement.
Two differences survive that round trip, on this verb and on
ptah migrations generate alike. The restored primary key takes the server’s
default name rather than the name it had, because the table body has nowhere to
put one. A column that was UNIQUE comes back both from the table body and from
the named constraint, so the restored table holds two unique constraints where
it held one. Neither stops the rollback from applying; both mean a
schema diff immediately after a rollback can report work to do.
ptah-compat migrate lint
Section titled “ptah-compat migrate lint”Runs Ptah migration linting with Atlas --dir-format defaulting to atlas.
It reads every Atlas source layout under both spellings, so a directory
another tool wrote can be linted without importing it first:
ptah-compat migrate lint --dir "file://migrations?format=golang-migrate" \ --dev-url "sqlite://dev.db" --latest 1The checksum step covers the file set that layout’s atlas.sum covers, not the
Atlas one: on a golang-migrate directory, editing the covered *.up.sql
fails the lint and editing the uncovered *.down.sql does not, and a Flyway
sub/V2__nested.sql is covered.
Flyway lint reports exact source tokens in its migration detail lines. A single
repeatable uses a blank version detail and the generic analysis header, matching
the pinned community binary. If replay of an exact empty identity fails, the
error names it as "" rather than leaving an anonymous gap in the diagnostic;
numeric and nonempty identities retain their ordinary spelling.
| Flag | Behavior |
|---|---|
--latest N |
Positive N selects the latest revision keys and remains exclusive with --git-base. Zero disables latest selection and configured lint.latest; explicit or configured Git may still select. With neither, the command returns --latest or --git-base is required. |
--git-base, --git-dir |
Map to native changeset linting. --git-base is the alternative to --latest. Changed Atlas repeatable files are selected by R or <number>R, not by a lossy numeric version. |
--dev-url |
Required. Infers the lint dialect, and cleans and replays migrations on directly connectable dev databases. |
--format |
Atlas Go-template output over .Env, .Steps, and .Files. The default is Atlas’s migration-analysis text report. |
Both requirements are the community binary’s, reproduced word for word — a run
missing --dev-url answers required flag(s) "dev-url" not set and one naming
no changeset answers --latest or --git-base is required, each at exit 1. Either
selector may come from the selected atlas.hcl env instead of the command line.
PTAH_ATLAS_LINT_WITHOUT_DEV_URL=1 runs the analysis with no dev database, which
Ptah can do and that binary cannot.
A docker:// dev database is provisioned: the container is started, used and
removed by the command. Atlas web reports remain an explicit gap.
Native twin: ptah migrations lint.
ptah-compat migrate new
Section titled “ptah-compat migrate new”Creates a skeleton migration and updates atlas.sum; the native equivalent is
ptah migrations create. Every Atlas source layout is supported under both
spellings, and the created files follow the selected tool’s convention:
| Layout | Files created |
|---|---|
atlas |
<version>_<name>.sql, empty |
golang-migrate |
<version>_<name>.up.sql and .down.sql, both empty |
flyway |
V<version>__<name>.sql and U<version>__<name>.sql, both empty |
goose |
<version>_<name>.sql holding -- +goose Up / -- +goose Down |
dbmate |
<version>_<name>.sql holding -- migrate:up / -- migrate:down |
liquibase |
<version>_<name>.sql holding --liquibase formatted sql |
atlas.sum is rewritten over the set the selected layout covers, so
golang-migrate and flyway create two files and cover one. On atlas,
--edit opens the created file in $VISUAL/$EDITOR before atlas.sum is
refreshed; on every other layout it is refused, as it is by Atlas.
For a project migration.dir backed by data.template_dir, the new root SQL
file and atlas.sum are synchronized to the template source directory. The
existing templates remain unchanged and the command stays silent.
A migration name is required on a non-atlas layout. Atlas accepts an omitted
name and writes the version alone, but such a file is one Ptah’s own
migrate apply cannot read back on golang-migrate, goose, liquibase and
dbmate, so it is not created.
A migration name may not contain a path separator on this verb or on
migrate diff: the name becomes part of the file name, so a / in it selects a
directory that is not there. The run is refused and nothing is written, matching
the community binary’s refusal of the same input
(#1231). Nothing else about a
name is refused — a space, a backslash and .. are accepted, as they are there.
The directory’s existing atlas.sum is verified first — over the selected
layout’s covered file set — with the same output migrate apply and
migrate validate produce, and nothing is created when the check fails; see
Which verbs enforce atlas.sum.
An unrecognized --dir query key is ignored here and named on standard error,
as it is on the other seven verbs that accept a --dir query — apply,
diff, hash, lint, set, status and validate. It is not ignored on
checkpoint, down, edit, rebase, rm or test: those refuse a --dir
query outright, as the shared rules above record.
--dir must name a scheme on migrate new, diff, hash, validate,
status, and lint: --dir migrations is refused on those consumers with
missing scheme for dir url. Did you mean "file://migrations"? and creates
nothing. The stderr line ends with the bytes 20 0a: one ASCII space followed
by the line feed. The same applies to its PTAH_DIR twin. A directory selected
by atlas.hcl migration.dir still accepts a bare path
(#1186).
Omitted entirely, --dir defaults to file://migrations, so
ptah-compat migrate new add_users creates ./migrations and writes into it
(#1241). Missing parents are
created too: --dir file://db/migrations creates db and db/migrations. A
path component that exists and is not a directory is still refused, and nothing
is written. See
The --dir default for how
the default ranks against PTAH_DIR, PTAH_MIGRATIONS_DIR and atlas.hcl.
ptah-compat migrate set [version]
Section titled “ptah-compat migrate set [version]”Moves Atlas revision history to the positional version without executing
migration SQL, with Atlas revision-table metadata and Atlas-format migration
directories by default. With --env, reads env.url, migration.dir, and
migration.revisions_schema from atlas.hcl; explicit --dir, --url, and
--revisions-schema flags keep CLI precedence. Flyway operands match exact
source tokens byte for byte, so 01 and 1 name different migrations. An
explicit empty positional operand selects a single repeatable’s empty token.
The success summary renders that identity as "", both on the current-version
line and beside the changed revision, so the present token is not an anonymous
gap in operator output.
When the directory no longer owns an applied identity, metadata moves only if
the stored row preserves enough Flyway role information to order it against the
target. A known retired baseline stays before versioned migrations regardless
of its token. A known retired versioned row uses numeric component order
against a versioned target, but uses Flyway’s raw-token squash cut against a
surviving baseline target: for example, B2 keeps retired V10 because "10"
sorts before "2". The same raw-token order applies when both migrations are
known baselines: selecting B3 keeps retired B2, while selecting B10 removes
retired B20.
A single repeatable target follows retired versioned history even
though its exact identity is empty. This is deliberately safer
than Atlas CE v1.3.0: CE exits 0 but deletes the retired versioned row,
while Ptah exits 0 and keeps both revision identities. Rows that do not
distinguish those roles, including ordinary Atlas CE applied rows, refuse
before any revision row changes instead of guessing from token bytes.
Native twin:
ptah migrations set.
ptah-compat migrate down
Section titled “ptah-compat migrate down”Forwards to ptah migrations down with mapped Atlas flags.
| Flag | Behavior |
|---|---|
--dev-url |
Replays and verifies the rollback plan on the dev database before the target is touched (native --shadow-db). |
--format |
Flag or PTAH_FORMAT; renders an Atlas Go-template report. Real and dry-run rollbacks are non-interactive. |
--revision-format |
Defaults to the atlas table layout, like migrate set. The native ptah pass-through selects the ptah layout. Both retain recoverable failed-down state. |
Because the forward defaults to the Atlas revision-table layout, a bare invocation
reverts the revisions ptah-compat migrate apply wrote.
A failed rollback stays dirty even with the Atlas layout. Resume it with
ptah migrations repair --dir-format atlas --revision-format atlas, using the
same database, directory, revision schema, version, and required
--resume-from statement as the failed compat run.
The command starts a real rollback without reading stdin, matching Atlas. It
does not accept the native --confirm flag. Review --url, --dir, and
--to-version before running it. Native ptah migrations down keeps its
interactive confirmation.
--to-tag rolls back to the version a tag names. Tags are recorded with ptah migrations tag and live in the database beside the revisions, so resolving one
contacts nothing:
ptah migrations tag release-v1 --db-url "$DATABASE_URL" --version 20260801000001ptah-compat migrate down --url "$DATABASE_URL" --dir file://migrations --to-tag release-v1A tag nobody recorded is refused rather than resolved to zero, because the
rollback target defaults to 0 and a discarded tag would revert the whole
history where the operator asked for something bounded. Passing --to-version
as well is refused too: both name where to stop, and picking one silently would
roll back to a version nobody chose.
--skip-checks bypasses the pre-migration checks the down bodies being rolled
back carry — the -- +ptah check directives that abort a rollback when their
assertion does not hold. It means on this verb exactly what it means on
migrate apply, including through PTAH_SKIP_CHECKS.
--plan derives the rollback from the schema difference instead of running the
down bodies, which is what makes a migration with no down body revertible:
ptah-compat migrate down --url "$DATABASE_URL" --dir file://migrations \ --to-version 1 --plan --dev-url "docker://postgres/16/dev"The target version’s schema is built on --dev-url and compared against the live
database, so the flag needs one: that schema exists nowhere else — not in the
live database, which is what is being changed, and not in any single file, since
a version’s schema is the accumulation of every migration up to it.
Ptah’s own revision and tag tables are excluded from the comparison. The live database and the dev replay can name them differently, and a table one side has and the other does not otherwise looks exactly like a table the rollback should drop.
A derived plan is an inference about structure, while a down file is a statement
of intent. A down body can preserve data a derived DROP will not, and can order
operations in a way a structural comparison has no reason to choose. That is why
--plan is opt-in and the authored bodies stay the default.
ptah-compat migrate diff
Section titled “ptah-compat migrate diff”Verifies the directory’s atlas.sum, replays the selected migration layout on
--dev-url, diffs it against --to, and writes new migration files in that
layout. atlas.sum updates only after every file was written; a failed write
rolls the whole generation back.
For a project migration.dir backed by data.template_dir, replay uses the
immutable rendered snapshot while publication writes the new root SQL files and
atlas.sum to the template source directory. Existing templates are not
rewritten, and a successful writing run is silent so the backing host path does
not leak through the virtual migration URL.
The checksum refusal comes first — before the dev database is connected to and
before --to and --dev-url are required at all, which is the order Atlas uses
— so nothing is created on a directory it refuses. A directory that has never
been hashed and already holds a migration is refused; one that does not exist
yet, or holds no top-level *.sql, is not, which is how a project’s first
migration gets written. An unrecognized --dir query key is ignored;
?format= and --dir-format select any of the six writable layouts. The
directory is verified over that layout’s covered file set before the dev
database is opened.
Goose carries a whole-file -- +goose NO TRANSACTION directive when either the
forward or exact reverse plan requires no-transaction execution. The directive
governs both sections. golang-migrate, Flyway, dbmate, and Liquibase remain
fail-closed for those plans because their safe transaction metadata has not
been proven. The Atlas layout remains forward-only and carries -- atlas:txmode none on its own file when required.
Both spellings of the layout are read the way the other verbs that accept a
--dir query read them. The value is matched verbatim, so --dir-format ATLAS
and --dir-format " atlas " are rejected rather than coerced, and an explicit
?format= outranks --dir-format — --dir "file://migrations?format=atlas" --dir-format golang-migrate writes the Atlas-layout migration. An
unrecognized query key selects no layout, so --dir-format still decides
there.
The verb takes at most one positional, the migration name, and a second one is
refused with accepts at most 1 arg(s), received 2
(#1231). The name may not contain
a path separator, checked where the file would be written: a diff that finds no
changes writes nothing, never reaches the name, and still exits 0 — which is
what the community binary does.
Desired state (--to) accepts one of: local .hcl, .yaml, .yml, or
.sql files; one directly connectable database URL; one local Atlas migration
directory; or one env:// reference into the evaluated atlas.hcl
environment. Source kinds cannot be mixed, the database source must use the
--dev-url dialect, and a desired database must not identify the same database
as --dev-url.
Flags
| Flag | Behavior |
|---|---|
--dry-run |
Atlas-hidden; prints the generated SQL instead of writing files. |
--format |
Renders generated SQL with sql and .MarshalSQL templates. The default is Atlas-style two-space indentation. |
--schema/-s |
Narrows the current and desired schemas used for comparison and output. |
--edit |
Opens the generated migrations in $VISUAL/$EDITOR before atlas.sum is finalized. |
--env |
Reads env.schema.src, env.dev, migration.dir, format.migrate.diff, and supported diff policy from atlas.hcl. |
--schema narrows comparison only: migration replay and cleanup still own the
complete dev database realm.
Concurrent indexes. With diff.concurrent_index.create, new indexes are
planned as CREATE INDEX CONCURRENTLY and their files are tagged with the
Atlas -- atlas:txmode none directive, splitting mixed plans into a
transactional file followed by a concurrent-index file. Unsplittable mixes are
refused.
--lock-timeout bounds waiting for both Ptah’s local migration-directory
lock and the exclusive dev-database lock:
- PostgreSQL, YugabyteDB, MySQL, MariaDB, and SQL Server use session advisory locks;
- SQLite, ClickHouse, and CockroachDB use an operating-system lock keyed by normalized database identity;
- dialects without a safe dev-database lock fail before cleanup.
Cross-host ClickHouse and CockroachDB replay is unsupported.
--qualifier prefixes every object in the generated statements with a
custom schema qualifier on PostgreSQL-family, MySQL, and MariaDB dev databases.
Invalid values, unsupported dialects, multi-schema plans, and statement kinds
Ptah cannot re-qualify yet (for example enum types) fail explicitly before any
file or checksum is written.
A docker:// dev database is provisioned: the container is started, used and
removed by the command.
Native twin: ptah migrations generate.
ptah-compat migrate import
Section titled “ptah-compat migrate import”Imports local file:// migration directories from atlas, golang-migrate,
goose, flyway, liquibase, or dbmate format into a separate Atlas
single-file directory and writes atlas.sum. Flyway repeatable migrations are
converted to one-time versioned Atlas files rather than emitted with an R
suffix, so the imported directory remains stable under Ptah’s revision model.
A conventional Liquibase formatted-SQL name such as changelog.sql makes the
importer parse every covered SQL file and emit one numeric Atlas file per
changeset, in lexical file and changeset appearance order. Versions are padded
to the digit width of the final version so lexical checksum order stays numeric.
Headerless or malformed members refuse the whole import before destination
creation.
A successful compatibility import is silent; inspect the destination directory
and its atlas.sum instead of relying on a progress message. Failures are still
reported on stderr. The native ptah migrations import converts the same source
formats into Ptah-native migrations instead, and reports what it wrote.
--from and --to are resolved by the same rules as every other verb’s
--dir. Both require a scheme, the source layout comes from --from’s
?format= query or from --dir-format with the query winning, and the value is
matched verbatim — FLYWAY and " flyway " are refused. An empty ?format=
selects the Atlas layout and outranks --dir-format, which makes the import a
no-op that is refused rather than performed.
Refusals are answered in Atlas’s order: the source scheme, then the layout value, then whether the source directory exists, then whether it is already in the Atlas layout, then the target scheme. A source directory that is not there is reported as missing rather than as a layout conflict.
ptah-compat migrate checkpoint [name]
Section titled “ptah-compat migrate checkpoint [name]”Forwards to ptah migrations checkpoint, replaying the migration directory on
the --dev-url dev database and writing a cumulative-schema checkpoint.
| Argument | Maps to |
|---|---|
--dir |
The native migrations directory. |
| Positional name (optional) | The checkpoint description, used as the file-name stem. |
--dir-format=atlas |
Writes the Atlas single-file checkpoint (default). |
--dir-format=ptah |
Writes the ptah reversible checkpoint pair. |
-s, --schema |
The native --schemas allow-list; repeat the flag or pass one comma-separated value. |
--qualifier |
The native --qualifier: prefixes every object the checkpoint creates with a schema qualifier. |
--lock-timeout |
The native --migration-lock-timeout, bounding the wait for the dev database’s migration lock during the replay. |
--edit |
The native --edit: opens the written checkpoint files in $VISUAL, then $EDITOR, and refreshes the directory checksum afterwards. |
--edit refuses before it replays anything when the session could not finish:
with no editor configured, and when standard input is not a terminal. The second
refusal is the one that matters in CI, where an interactive editor launched
without a terminal does not fail but waits. Set
PTAH_ALLOW_NONINTERACTIVE_EDIT=1 when $EDITOR is a script that edits and
exits on its own; that is an environment variable rather than a flag because
Atlas registers no flag for it. The native --editor selects a specific editor
command and is deliberately not part of this verb’s Atlas flag surface.
--lock-timeout bounds a lock only on dialects that implement advisory locking.
On one that does not — SQLite, ClickHouse — the run says so on stderr rather than
accepting a bound that binds nothing.
--qualifier is refused on a dialect Ptah cannot qualify, so a checkpoint is
never written half-qualified.
Not registered here: --format, whose Go-template report needs the compat
formatting path this verb does not have yet, and --lock-name, which belongs to
the named-lock family.
--dir-format selects the checkpoint convention, and on this verb it
defaults to atlas, matching the default Atlas registers and every other
compat migrate verb:
atlaswrites one up-only file,<version>_<name>.sql, whose first line is the-- atlas:checkpointdirective, and refreshesatlas.sum. The version is a UTC timestamp, raised to one above the newest migration in the directory when that is higher — including migrations in subdirectories, which the replay and the reader both see but a bare timestamp would sort below. There is no down file: the Atlas format is up-only, so an Atlas checkpoint is not reversible.ptahwrites the reversible pairNNNNNNNNNN_<name>.checkpoint.up.sql/.checkpoint.down.sqland refreshesptah.sum.
The native ptah migrations checkpoint keeps ptah as its default; only the
compat surface defaults to atlas. Native accepts --dir-format=auto as a
spelling and refuses it with a named message, because writing under it would
have to guess the file convention and which integrity file to refresh. On the
compat surface auto is not an Atlas directory format at all, so it draws the
ordinary unknown-format rejection instead.
A directory must end up with one integrity file and one file convention, so two shapes are refused before anything is written:
- a checkpoint that would leave both
ptah.sumandatlas.sumbehind, which--dir-format autocannot read; and - an Atlas-format checkpoint into a directory holding ptah-convention
migrations, hashed or not. There the checkpoint would be permanently
invisible: discovery reads the ptah files and never sees it, while
verification finds the
atlas.sumand reports the directory as valid.
Re-hash or convert the directory into one format first.
The read side honors the -- atlas:checkpoint directive either way: applying a
checkpoint directory bootstraps a fresh database from the latest checkpoint and
silently skips the checkpoint on a database that already applied the
pre-checkpoint history, matching measured Atlas behavior.
Atlas keeps migrate checkpoint in its Pro build — the pinned CE binary
registers the verb but aborts with “not supported by the community version” and
registers none of its own flags — so this is a free Ptah capability rather than
an Atlas CE stub.
?format= on this verb’s --dir URL is still refused; use --dir-format. CE
aborts every migrate checkpoint invocation, so there is no CE behavior to
diverge from here and refusing an unimplemented spelling loudly is the intended
outcome. The eight verbs that accept a --dir query honor the parameter;
migrate diff writes forward and reverse SQL in the selected layout.
ptah-compat migrate test [paths]
Section titled “ptah-compat migrate test [paths]”Forwards to ptah migrations test.
| Atlas flag | Native equivalent |
|---|---|
--dir |
The native migration directory, Atlas-format by default via --dir-format. |
--dev-url |
The native throwaway database; an ephemeral SQLite database when omitted. |
--run |
The native case-name filter. |
--revisions-schema |
The native --migrations-schema: the schema a migrate_to step records revisions in. |
| Positional path (optional) | The directory of Ptah-native YAML test cases, default ./tests. |
Exit codes match the native runner: 0 when all cases pass, 1 on test failure.
Atlas keeps migrate test in its Pro build, so this is a free Ptah capability
rather than an Atlas CE stub.
ptah-compat migrate edit {name | version}
Section titled “ptah-compat migrate edit {name | version}”Forwards to ptah migrations edit: the positional maps to the native
--version (a migration file name contributes its leading version digits),
--dir maps to the native migration directory (Atlas-format by default via
--dir-format), the editor resolves from $VISUAL, then $EDITOR, and the
directory checksum is rewritten afterwards. Atlas keeps migrate edit outside
its community build, so this is a free Ptah capability rather than an Atlas CE
stub.
ptah-compat migrate rebase {name | version}
Section titled “ptah-compat migrate rebase {name | version}”Forwards to ptah migrations rebase: re-timestamps the selected migration
past every existing version and rewrites the directory checksum. Multiple
positional values and a...b version ranges are rejected loudly; forward one
migration per run. Atlas keeps migrate rebase outside its community build,
so this is a free Ptah capability rather than an Atlas CE stub.
ptah-compat migrate rm {name | version}
Section titled “ptah-compat migrate rm {name | version}”Forwards to ptah migrations rm: deletes the selected migration’s files and
rewrites the directory checksum. Atlas keeps migrate rm outside its
community build, so this is a free Ptah capability rather than an Atlas CE
stub.
ptah-compat migrate push
Section titled “ptah-compat migrate push”Registered but not implemented because the command targets an account-bound
hosted registry protocol. --help reports that the command is not implemented
and exits 0; direct execution reports the same status and exits 1. The open
replacement is the native ptah migrations push to any OCI registry.
Schema commands
Section titled “Schema commands”ptah-compat schema inspect
Section titled “ptah-compat schema inspect”Inspects the --url source and writes Atlas-compatible schema output without
Ptah status banners.
Sources (--url) accepts one of: a live database URL; a local .hcl,
.yaml, .yml, or .sql schema file; a migration directory (a directory
containing atlas.sum); or an env:// reference resolved through the
evaluated atlas.hcl env.
Non-database sources require --dev-url and are evaluated on the dev database:
it is reset, the source is materialized on it (schema files executed, migration
directories replayed), and the result is introspected. Inspecting a file
without --dev-url fails with Atlas’s --dev-url cannot be empty message.
Output formats
| Output | How to request it |
|---|---|
| HCL | The default, or --format '{{ hcl . }}'. |
| SQL | --format '{{ sql . }}'. |
| JSON | --format '{{ json . }}'. |
| Custom templates | {{ .MarshalHCL }}, {{ hcl . }}, {{ sql . }}, {{ mermaid . }}. |
Bare --format hcl, --format sql, and --format json write those literal
words. They add no line feed, and database contents do not change the values.
Surrounding whitespace is also preserved: --format ' hcl ' writes hex
20 68 63 6c 20, with no line feed. Those literal cases match Atlas CE v1.3.0.
Native ptah schema inspect --format hcl|sql|json keeps its rendered
shorthands.
Split-write exports. {{ hcl . | split | write "schema" }} and
{{ sql . | split | write "schema" }} support the documented Atlas split
strategies: per object (the default, with a main.sql atlas:import entry
point for SQL), split "schema", and split "type", plus an optional
file-extension argument.
Exports render one output plan applied by a single writer. Duplicate output
paths, traversal or escape from the output directory, planned file/directory
collisions, and existing-directory destinations fail explicitly before anything
is written. The pinned Atlas CE binary rejects split, write, and hcl as
non-community template functions, so these exports are an open Ptah extension.
Filtering
--schema/-snarrows inspection when supported by the database reader.--includepositively selects the top-level resources that survive, with Atlas-style globs and[type=...]selectors. Repeated and comma-separated values union. Composition order is--schema, then--include, then--exclude. A selection that matches nothing renders no objects and keeps exit status 0, and reports itself on standard error; an empty value carries no selection and leaves inspection unfiltered.- The OSS
--excludeflag filters inspected resources with Atlas-style globs and[type=...]selectors, including the Atlas-documented*[type=extension].versionfield selector with schema-qualified globs. - Child resources (columns, indexes, constraints, triggers, policies, grants)
cannot be included on their own with
[type=column], which fails before any database is contacted. - A positional spelling such as
table.columnis not refused on its shape: it is indistinguishable from a table literally named that. An identifier holding a dot is therefore selectable asmain."my.table",a\.b\.c, or barea.b.c. - Whether a selector matched is decided by the projection, not by the selector
text:
path.Matchtreats.as an ordinary character, sotable.column,table*column,table?column, andtable[.]columnall reach past a top-level resource and select nothing.schema applyandschema diffrefuse an empty--includeselection;schema inspectkeeps exit status 0 and reports it on standard error. - A selection that drops a dependency of a selected object is refused rather than rendered.
- Other field-level exclude selectors fail explicitly, and the refusal names
the fields the selected resource types do support. Type selectors on
non-final pattern segments fail too, except for the leading
[type=schema]segment documented under Leading schema type selector; exporter blocks remain an explicit gap.
Subtractable fields
Section titled “Subtractable fields”A field is subtractable when Ptah can remove it from the inspected document and still write a document that means what the database holds. That is the whole criterion, and it is narrower than “a field the reader captured”: a column’s type is captured and cannot be subtracted, because a column without one is not a column. A comment can go, and the table is still that table.
| Resource type | Subtractable fields |
|---|---|
base_table |
comment |
extension |
version |
materialized_view |
comment |
synonym |
comment |
table |
comment |
view |
comment |
.* names every field in the row for the selected type. A selector naming
anything else is refused rather than ignored, because a selector that silently
does nothing is how a user comes to believe a field was excluded.
Adding a field is a change to excludeFieldSelectors in
internal/atlasfilter, and this table is checked against it
(TestExcludeFieldSelectors_MatchTheDocumentedSet) — the two cannot drift.
The pinned Atlas CE binary rejects schema inspect --include with
unknown flag: --include; Atlas registers it. The measured
behavioral differences are tabulated under
schema inspect --include.
Native twin: ptah schema inspect.
ptah-compat schema apply
Section titled “ptah-compat schema apply”Diffs a live database against the --to desired state, prints the planned SQL,
and applies it after interactive confirmation or explicit --auto-approve.
Desired state (--to) accepts one of:
- local
file://.hcl,.yaml,.yml, or.sqlschema files; - one
file://directory of.sqlor.hclschema files, read in filename order as an ordered script — the two formats together are ambiguous, other extensions are ignored, an empty directory is refused, a subdirectory is refused rather than descended into, and a file that declares an object an earlier file already declared is refused (read state from "2_b.sql": table "users" already exists) unless its declaration carriesIF NOT EXISTSorOR REPLACE; - one directly connectable database URL;
- one migration directory (a
file://directory containingatlas.sum) replayed on the required--dev-urldev database; - one
env://<attribute>reference (src,schema.src,url,dev,migration.dir) resolved through the evaluatedatlas.hclenv.
All --to values must be one source kind, database and migration-directory
sources accept one URL, and unsupported schemes such as atlas:// fail before
the target database is contacted.
Flags
| Flag | Behavior |
|---|---|
--dry-run |
Prints the plan without applying. Mutually exclusive with --auto-approve on the command line. |
--auto-approve |
Applies without the interactive confirmation. Mutually exclusive with --dry-run on the command line. |
--tx-mode |
file and all execute the generated plan in one transaction; none executes statements without transaction wrapping. |
--format |
Atlas-style templates over planned changes with sql, .MarshalSQL, and the shared helper set including json. {{ json . }} renders {Driver, URL, Changes{Applied|Pending}}. |
--exclude |
Filters matching resources out of both sides of the comparison before planning, as do disabled schema.mode values. |
--edit |
Opens the planned SQL in $VISUAL/$EDITOR before approval; the edited SQL is what gets applied. |
--file/-f |
Atlas’s hidden alias, accepted for local HCL or SQL paths. |
--env |
Reads env.url, env.src, env.schema.src, env.dev, env.exclude, env.schema.mode, format.schema.apply, and supported diff policy from atlas.hcl. |
--dry-run and --auto-approve contradict each other — one asks for the plan
and no execution, the other for execution with no prompt — and the pair is
refused rather than silently resolved
(#1231):
Error: if any flags in the group [dry-run auto-approve] are set none of the others can be; [auto-approve dry-run] were all setThe rule reads the command line, not the environment. PTAH_DRY_RUN is not a
typed --dry-run for this purpose, so a wrapper that exports it does not turn
every --auto-approve in the pipeline into a refusal: the run behaves the way
--dry-run alone does, printing the plan and applying nothing.
PTAH_DRY_RUN=1 ptah-compat schema apply -u "$DATABASE_URL" \ --to file://schema.sql --dev-url "$DEV_URL" --auto-approve# Planned schema changes: … (exit 0, nothing executed)The variable does not work in the other direction either. Typing both flags is
still typing both, so adding --dry-run to the command line above is refused
with the same sentence whether or not the variable is exported.
--env evaluation includes local variable defaults, locals, getenv, file,
fileset, format, jsondecode, jsonencode, tolist, and the supported
hcl_schema, sql, external, runtimevar, and template_dir project data
sources. Data sources execute only when the selected config depends on them;
dependency order is resolved before command settings are read.
--schema/-s and --include scope both sides of the comparison.
--schema restricts them to the named schema scopes; --include positively
selects top-level resources with Atlas-style glob selectors and [type=...]
filters. Repeated values union deterministically, --exclude plus disabled
schema.mode values subtract afterward, cross-scope dependencies refuse the
plan with explicit diagnostics, and an explicit include selection matching
nothing refuses the apply.
For a live PostgreSQL desired schema, a selected extension outside the default
schema retains its installation schema. A create plans CREATE SCHEMA IF NOT EXISTS followed by CREATE EXTENSION ... WITH SCHEMA ..., identical live
placements compare as synced, and drops remain supported. A placement change
is detected but fails before SQL output because Ptah does not yet plan ALTER EXTENSION ... SET SCHEMA.
--plan file://<path> executes a pre-approved local plan file instead of
re-planning. Both plan formats are accepted, detected by content: the Atlas
.plan.hcl shape and Ptah’s native format_version-1 .plan.json.
- A JSON plan is verified against its recorded source fingerprint — a drifted
target refuses with a stale-plan error — and may run without
--to. - An Atlas-format plan requires
--to, as Atlas does: its hashes are Atlas-computed with no local recipe, so the plan is replayed on a dev database from the target’s current schema, and the reached state must equal the--todesired state before the target is touched. SQLite targets get a throwaway dev database automatically; every other dialect requires--dev-url. - Before replaying, statements matching a deny-list of known escape
constructs are refused by name before anything executes. The lint covers
SQLite (
ATTACH/DETACH,VACUUM INTO, storage-directory pragmas,load_extension), PostgreSQL (DOblocks, routine bodies and dynamic SQL calling file-access ordblinkfunctions,COPY ... PROGRAMorCOPYwith a file path,postgres_fdw,file_fdw), MySQL/MariaDB (LOAD DATA INFILE,INTO OUTFILE/DUMPFILE,LOAD_FILE,ENGINE=FEDERATED,CREATE SERVER,INSTALL PLUGIN/COMPONENT,DATA/INDEX DIRECTORY), SQL Server (xp_cmdshell,xp_dirtree,OPENROWSET,OPENDATASOURCE,BULK INSERT,sp_addlinkedserver), and ClickHouse (URL,File,S3,HDFS,MySQL,PostgreSQLtable engines). - The lint is best-effort, not exhaustive, and it is not a sandbox. String
concatenation alone defeats any scanner, so a
--dev-urlmust point at a database you are willing to have a foreign plan file execute arbitrary SQL against. - Real enforcement exists only on SQLite dev databases — the ephemeral one
Ptah creates for SQLite targets, and an operator-supplied SQLite
--dev-url, since the restriction keys on the dev dialect. Their sessions refuseATTACH,DETACH, andVACUUM INTOat the engine level and cannot load extensions; Ptah verifies the restriction is in force before rehearsing and refuses to rehearse if it is not. Storage-directory pragmas andwritable_schemaare not covered, so the converges-to---toverdict is a good-faith check rather than an adversarial one. See Save and execute plan files. - The replay also runs under
--dry-run, so a plan can be verified without committing to apply it. - Whenever a desired state is available, the end state is verified again on the target after the apply and a mismatch fails loudly; the verification is always on, like Atlas’s.
- Registry
atlas://plan URLs are rejected.--plancannot be combined with--file,--exclude,--schema,--include, or--edit, and--dev-urlcombines with--planonly together with--to.
--lock-timeout bounds waiting for the session advisory lock that
serializes concurrent schema applies against one target. The lock is acquired
before target inspection and planning, held through simulation, confirmation,
and execution, and released on every exit path. Empty waits indefinitely, an
elapsed timeout fails before the target is inspected, and dialects without
advisory locks (SQLite, ClickHouse, CockroachDB, Spanner) proceed unlocked with
a stderr note. PostgreSQL, YugabyteDB, MySQL, MariaDB, and SQL Server take a
real lock.
--lock-name replaces the lock name for the run (ptah_schema_apply by
default). Runs serialize only against other runs naming the same lock, which is
both how a run coordinates with a different tool and how it opts out of the
default. An empty value is refused; on a dialect without advisory locks the
stderr note names the lock that was not acquired.
--skip-lock takes no lock at all: a lock another process holds is ignored
rather than waited on, so concurrent applies can interleave. It cannot be
combined with --lock-name.
--dev-url rehearsal. --dev-url is required whenever --to is not
already a live database, failing with Atlas’s --dev-url cannot be empty
message otherwise; a database --to needs none, and
PTAH_ATLAS_APPLY_WITHOUT_DEV_URL=1 restores planning without one. Before the
apply, --dev-url rehearses the exact ordered plan on the dev database — reset,
the target’s current schema recreated, then the planned (or edited) statements
executed under the same transaction mode. A failed rehearsal refuses the apply
with the target unchanged; the dev database must not be the target and must
share its schema scope. The rehearsal runs under --dry-run too, so a dry run
cannot report a plan the real apply would refuse.
Native twin: ptah schema apply.
ptah-compat schema plan
Section titled “ptah-compat schema plan”Computes the declarative migration from the --from target database to local
--to schema files and saves it as a local plan file. The default format is
the Atlas .plan.hcl shape — one plan block with from/to fingerprints
and the migration SQL — so the saved file is readable by Atlas’s plan reader;
an --output path ending in .json writes the native fingerprinted JSON
plan (format version 1) instead. Without --save/--output/--dry-run, the
plan document prints to stdout.
Flags
| Flag | Behavior |
|---|---|
--save |
Atomically writes <name>.plan.hcl, using an Atlas-style UTC timestamp default name or --name. Refuses to replace any existing entry at the default path, including a concurrent writer or symlink. |
--output <path>/-o |
Chooses the location and atomically replaces that entry; a .json path selects the native JSON plan format. The plan name recorded inside a JSON plan stays fingerprint-derived unless --name is given. |
--dry-run |
Prints the plan document without saving. |
--auto-approve |
Accepted for Atlas CLI compatibility; a locally saved plan file is approved by operator review, so there is no prompt to skip. |
--edit |
Opens the planned SQL in $VISUAL, then $EDITOR, and saves the plan rebuilt from valid UTF-8 text. Comments round-trip, and dialect-aware statement severity and the destructive marker are re-derived from what you wrote. An edit leaving no statement is refused, and nothing is written. |
--name-format <template> |
Templates the name over .FromHash and .ToHash; hashes use Atlas’s measured untagged standard-Base64 representation, and the Atlas template helpers (json, upper, add, indent_ln, …) are available. Cannot be combined with --name. A rendered / or \ requires explicit --output. |
--skip-lint |
Accepted as an explicit no-op: schema plan runs no lint step, so there is nothing to skip. |
--format <template> |
Renders the plan through a Go template instead of printing the plan document. See Plan report payload. |
--directive <line>/-d |
Writes a migration directive into the plan file. See Plan directives. |
--env |
Reads url (the plan target), schema.src, dev, exclude, schema.mode, and supported diff policy from atlas.hcl. |
The JSON plan records the ordered SQL statements with per-statement safety
severity, the dialect, the exclude patterns, and SHA-256 fingerprints of the
source and desired schema states. The .plan.hcl shape carries only the
name, the fingerprints, and the migration SQL; Ptah writes its own sha256
fingerprints there (Atlas parses the file but verifies its own
base64 hashes, which have no local recipe), re-derives statement severity at
read time, and refuses to save a plan computed with --exclude as
.plan.hcl because the shape cannot record the patterns.
The .FromHash and .ToHash field names and their untagged standard-Base64
representation were verified against Atlas’s own reference; their values still
differ because Ptah fingerprints its independent
representation.
Editing changes the statements, never the fingerprints. from still describes
the live source database, so apply-time staleness detection keeps working. to
still describes the schema the plan was computed against, which edited SQL may
no longer reach: schema apply replays an Atlas-format plan on a dev database
and requires it to converge on --to before touching the target, but a native
.json plan carries no such replay, so an edited JSON plan is only as good as
its review.
Everything that can refuse the plan without reading its statements — the
--exclude/.plan.hcl incompatibility, and every --name-format failure —
happens before the editor opens, so an edit is never thrown away over a problem
that was decidable beforehand.
A default plan name becomes a file name, so --name and --name-format
refuse path separators unless --output supplies the location separately.
Both flags always refuse control characters, ./.., and the characters
Windows forbids in a file name (:*?"<>|). Atlas’s documented
plan_{{ slice .ToHash 0 8 }} example works against Ptah’s untagged
standard-Base64 hash value; because standard Base64 can contain /, use an
explicit --output when the rendered name must never depend on file-system
path rules.
Plan report payload
Section titled “Plan report payload”--format takes the same Go template schema apply, schema diff,
migrate apply and the other reporting verbs take, and renders it instead of
printing the plan document. It selects the output rather than adding to it: a
plan asked to be saved is still saved, and the Plan saved to file://… line is
suppressed so it cannot land inside a rendered document.
The payload’s field names are Ptah’s. On the other eight verbs the shape
follows what Atlas prints, because Atlas executes those verbs and the names are
therefore observable; Atlas keeps schema plan in its Pro registry flow and
publishes help text for this flag and nothing a template could be written
against, so there was no shape to match — only one to choose and write down.
What it is chosen to match is the sibling verb: .Changes[].Cmd is
schema diff’s spelling, so a template written there reads a plan unchanged.
| Field | Meaning |
|---|---|
.Name |
Plan name, which is also the default plan file’s base name. |
.Dialect |
Target dialect the statements were rendered for. Empty for a plan read back from .plan.hcl, which has no field for it. |
.From, .To |
The plan’s source and desired-state fingerprints, the two values the plan file records. |
.Exclude |
Exclusion patterns the plan was computed with; omitted when there are none. |
.Destructive |
Whether any statement was classified destructive. |
.Changes |
Ordered planned statements, each with .Cmd, .Severity and .Reason. Always a list, so a synced schema renders an empty array rather than a sentence. |
.MigrationBody |
The plan file’s migration attribute exactly as written, directives included. Read this to reproduce the artifact; read .Changes or sql to describe it. |
sql renders the statements as one script and takes the same optional indent
argument it takes on schema diff. The shared helpers (json, json_merge,
upper, add, indent_ln, and the color helpers) are all registered — on
schema diff the shared set is behind PTAH_SCHEMA_DIFF_TEMPLATE_HELPERS
because Atlas offers sql alone there, and no such narrower surface exists on
a verb Atlas does not run at all.
$ ptah-compat schema plan --from "$DB" --to file://schema.sql --dry-run --format '{{ json . }}'$ ptah-compat schema plan --from "$DB" --to file://schema.sql --dry-run --format '{{ sql . }}'An empty or unparseable --format is refused before any database is opened,
and nothing is written.
Plan directives
Section titled “Plan directives”-d/--directive writes a migration directive into the plan file, in the
leading comment block its readers honor — the unbroken run of line comments
that starts the migration body, closed by a blank line. A directive below that
block governs nothing, which is why the position is not the operator’s to
choose.
Both spellings of a line are accepted: atlas:txmode none as the flag’s help
spells it, and -- atlas:txmode none as a migration file spells it.
| Directive | Honored by |
|---|---|
atlas:txmode none, atlas:txmode file |
schema apply --plan, which executes the plan in that transaction mode. |
atlas:nolint [<selector>…] |
schema plan lint, which silences the named rules over the plan’s SQL. |
Anything else is refused rather than recorded, including atlas:checkpoint:
a directive a plan file carries and nothing acts on is an instruction the
reviewer approves and the run ignores. atlas:txmode all is refused too, and
the refusal names file as the value to write instead — a transaction mode is
a file-level directive, and all is a global one.
A second atlas:txmode is refused whether or not it agrees with the first;
repeated atlas:nolint lines are ordinary, since each names its own selectors.
At apply time the directive resolves against --tx-mode under the rule a
versioned migration’s directive already answers to, through the same code: the
directive wins, except under --tx-mode all, where the combination is refused
rather than silently decided.
$ ptah-compat schema plan --from "$DB" --to file://schema.sql -o app.plan.hcl -d 'atlas:txmode none'$ ptah-compat schema apply --url "$DB" --to file://schema.sql --plan file://app.plan.hclNot implemented
- Registry-bound
--push,--pending, and--repoare recorded waivers that fail loudly. --schema,--include, and--lock-timeoutfail explicitly until implemented.- The registry sub-verbs (
approve,list,pull,push,rm) stay unsupported-boundary stubs: they arbitrate plan state in a remote registry. testandlintare implemented and have their own sections below. Both are separate verbs over a saved plan file, and neither puts a lint or test step onschema planitself, so--skip-lintis still a no-op there.
Atlas keeps schema plan in its Pro registry flow, so this is a free Ptah
capability rather than an Atlas CE stub.
Native twin: ptah schema plan.
ptah-compat schema plan new
Section titled “ptah-compat schema plan new”Creates a plan file for the schema transition. It is schema plan restricted
to the flag set Atlas registers on this sub-verb, with saving always on:
Atlas gives new neither --save nor --dry-run, and its documented purpose
is to create the plan file.
The plan is written to --output/-o when given, and to <name>.plan.hcl in
the working directory otherwise; an existing default-named plan file is never
overwritten. An --output path ending in .json writes the native JSON plan
format. --edit, --name, --name-format and --format behave exactly as they do on
schema plan, and the same refusals apply to --repo, --lock-timeout,
--schema and --include.
--save, --dry-run, --push, --pending, --skip-lint and --directive
are not registered here, because Atlas does not register them here.
ptah-compat schema plan validate
Section titled “ptah-compat schema plan validate”Checks that the plan file named by -f/--file describes the transition from
the --from target database to the local --to schema files, without changing
the target database. On success it writes nothing to stdout and exits 0.
Two checks run, and they are the two schema apply --plan runs before it
touches anything:
- the plan’s recorded from-fingerprint must match the live
--fromdatabase, for plans carrying Ptah’s ownsha256:fingerprints. An Atlas-written plan file carries Atlas hashes with no local recipe, so this check is skipped for those and the replay below is the only from-state gate. - the plan’s statements are replayed on a dev database seeded from the
target’s current schema, and the state they reach must equal
--to.
The replay always runs, in both plan formats — unlike schema apply --plan,
which may skip it for a fingerprint-verified native plan. A matching
from-fingerprint says the plan was computed against this database, not that its
statements reach --to, and the second question is the one this command exists
to answer. A SQLite target gets a throwaway dev database; every other dialect
requires --dev-url.
An explicit --dev-url is refused when it identifies the --from target,
even through an equivalent or percent-encoded SQLite path, a symlink or hard
link, or a network URL with different credentials, default-port spelling,
loopback alias, or driver-level endpoint/database override. Network URLs with
the same dialect and selected database name fail closed even across different
endpoints because DNS aliases and replicas cannot be proven independent.
Malformed target and desired-state URLs also fail closed. These shared checks
protect both schema apply simulation and schema plan validate, and run
before the dev database can be reset.
The plan’s SQL is not required to equal a freshly computed plan’s. Atlas
documents editing a saved plan’s migration attribute, so what is checked is
where the statements arrive, not how they are spelled.
--to is required; without it the command reports Atlas’s own wording,
the flag "to" is required to verify the provided plan. --exclude is
refused: a JSON plan records the patterns it was computed with and the Atlas
.plan.hcl shape records none at all, so flag-supplied patterns would verify a
different transition than the plan describes. Registry plan URLs
(atlas://…) are refused like they are on schema apply --plan.
ptah-compat schema plan lint
Section titled “ptah-compat schema plan lint”Analyzes the SQL of the plan file named by -f/--file and prints what Ptah’s
migration lint rules find, without changing the target database.
The plan is verified before it is analyzed, with the same two checks
schema plan validate runs and on the same terms, including the --dev-url
refusals above. A plan that fails either check is refused, and nothing is
analyzed: a report about a plan that does not describe this transition would be
an accurate report about a change nobody is about to make.
The analysis is the one ptah-compat migrate lint runs over a migration file
holding the same SQL — the same rules, the same codes, and the same
atlas:nolint directives silence it. The report goes to stdout: a header
naming the number of statements analyzed, the findings grouped by analyzer with
their suggested fixes, and a summary carrying the schema-change and diagnostic
counts.
Findings do not change the exit code. A plan carrying a destructive change exits 0 with the change described. A plan is a document an operator reviews and approves, the lint report is what they review it with, and a report that refuses on their behalf is one they cannot approve anything with. Two things follow, and both are deliberate:
- Set
PTAH_ATLAS_PLAN_LINT_FAIL_ON_ERROR=1when a pipeline needs the report to gate. An error-severity finding then exits 1, with the report still on stdout so the failure names its own reason. - Every run states on stderr what the report is a statement about. Ptah’s rule set is its own and does not name every hazard a schema change can carry, so “no diagnostics found” means “these rules found nothing”, not “this plan is safe”. Lint rules records what each rule covers. The statement is printed on the clean run too, because that is the run that reads as an all-clear; it goes to stderr so a pipeline capturing stdout keeps a document of findings and nothing else.
--format is refused, as it is on the sibling plan verbs: rendering the
analysis through a caller’s template is a second output contract, and the one
this verb has is the report on stdout. --exclude, --repo,
--lock-timeout, --schema and --include are refused for the same reasons
they are on schema plan validate.
Evidence for the three local sub-verbs
Section titled “Evidence for the three local sub-verbs”The flag sets of new and validate match a sanitized standard Atlas
v1.3.0 help bundle captured on 2026-08-02 with the exact binary and artifact
SHA-256 values pinned in testdata, and the published
Atlas CLI reference. The flag set of lint
comes from that published reference alone; it carries the same set as
validate, and the two are written out separately in the tests so a later
divergence in one cannot silently rewrite the assertion for the other. Their
behavior is not established by help, and Atlas Community Edition (CE)
settles nothing because it aborts the entire schema plan path. Ptah records
that limitation in tests and documentation instead of printing development
provenance during normal commands; successful new and validate runs keep
stderr empty, and a lint run keeps stdout to its report. Runtime parity
remains tracked in
stokaro/ptah#1037.
The validation tests do not rely only on plans written by Ptah. They also run
an Atlas-produced .plan.hcl artifact against live
SQLite, then mutate the source, desired schema, migration SQL, HCL syntax, and
statement set. Its versioned bundle includes source and desired SQL plus a
manifest of file hashes, known capture facts, and the original evidence that
was not preserved. Every invalid semantic variant is refused without changing
the target. Structurally valid Atlas Base64 SHA-256 from and to hashes are
treated as unauthenticated metadata because their derivation is not public;
malformed values are rejected. The replayed end state, not a foreign hash, is
the integrity boundary.
ptah-compat schema plan test [paths]
Section titled “ptah-compat schema plan test [paths]”Runs test "plan" cases from Atlas .test.hcl files against a throwaway
database. A case establishes a starting state, applies a saved plan file, and
asserts what the plan did:
test "plan" "add_email" { schema { url = "file://snapshots/v1.sql" }
exec { sql = "INSERT INTO users (id, name) VALUES (1, 'Ada')" }
apply { url = "file://plans/add_email.plan.json" }
exec { sql = "SELECT email FROM users WHERE id = 1" output = "ada@example.com" }}| Block | Does |
|---|---|
schema { url } |
Brings the database to the state that source describes. Applied through the same convergence path apply_schema uses. |
apply { url } |
Reads the plan file and runs its statements, through the same reader and executor as schema apply --plan. |
exec { sql } |
Runs a statement. With output, runs it and compares the first result. |
Steps run in the order they are written, which is what makes a case mean
anything: a plan describes a transition from a state, so schema has to
establish that state before apply.
Before the plan runs, its recorded from-state is checked against the database the case established. A snapshot that has drifted away from the state the plan was computed for is testing that plan against a state it was never meant for, and the case fails rather than reporting whatever the statements happened to do there. Only a plan carrying Ptah’s own sha256 fingerprint is checked: an Atlas-authored plan’s hashes have no local recipe.
The plan’s own statements are executed, never recomputed. A plan file that stopped matching what the planner now produces is precisely what this verb exists to catch, and recomputing would hide it.
--dev-url names the throwaway database, and takes a docker:// value like
every other verb that provisions one. Without it a SQLite database is created
per case and removed afterwards. --run filters cases by name, and a filter
matching nothing is refused rather than reported as a pass.
test "schema" and test "migrate" cases in the same files are left to
schema test and migrate test.
lint was deferred alongside it for a different reason, and that reason is
answered rather than outstanding. The worry was a linter in a gating position
reporting clean on a plan a wider rule set would flag. Ptah’s answer is to take
the gate out of the default and put the coverage on the output: the verb
reports, the exit code is not a verdict unless a pipeline asks for one with
PTAH_ATLAS_PLAN_LINT_FAIL_ON_ERROR, and every run says on stderr that a
report without findings describes the rules rather than the plan. The analyzer
coverage itself is not private to this verb — it is the published set in
Lint rules, reported identically by ptah-compat migrate lint.
Registry sub-verbs
Section titled “Registry sub-verbs”ptah-compat schema plan approve, list, pull, push and rm are
registered and not implemented, because each targets an account-bound hosted
registry protocol. --help reports that the command is not implemented and
exits 0; direct execution reports the same and exits 1:
$ ptah-compat schema plan listError: ptah-compat schema plan list is not implemented by Ptahnew, validate, lint and test are the sub-verbs that run locally, and
each has a section above.
ptah-compat schema diff
Section titled “ptah-compat schema diff”Diffs two desired-state sources and prints migration SQL.
Sources. Each of --from/-f and --to accepts one of:
- local
file://schema files with.hcl,.yaml,.yml, or.sqlextensions; - one directly connectable database URL, whose live schema is introspected;
- one migration directory (a
file://directory containingatlas.sum) replayed on the required--dev-urldev database; - one
env://<attribute>reference resolved through the evaluatedatlas.hclenv.
Unsupported schemes such as atlas:// fail during validation. The SQL dialect
is pinned by --dev-url first, then by --from and --to database URLs; local
schema files alone still require --dev-url.
Flags
| Flag | Behavior |
|---|---|
--format |
Atlas-style templates with sql and .MarshalSQL. |
--exclude |
Filters resources out of both sides before diffing, as do disabled schema.mode values. |
--schema/-s, --include |
Positively scope both sides, with the same selection semantics as schema apply. |
--env |
Reads env.schema.src, env.dev, env.exclude, env.schema.mode, format.schema.diff, and supported diff policy from atlas.hcl. |
Selection order matches schema apply: schema universe for schema-owned
resources, include selection, exclusion last, and cross-scope dependency
diagnostics. Database-wide extensions remain on both sides regardless of
installation placement. An extension-only --include selects their qualified
or bare identities; when a non-extension resource matches, all extensions ride
as non-removing support even beside extension selectors. Schema-only and
extension-only scopes remain authoritative for extension removal. Exclusions
still subtract afterward. A selection that matches neither side exits 1 with
no diff output rather than reporting a synced schema.
Native twin: ptah schema diff.
ptah-compat schema fmt
Section titled “ptah-compat schema fmt”Formats local .hcl files using HCL canonical layout. Native twin:
ptah schema fmt.
ptah-compat schema clean
Section titled “ptah-compat schema clean”Cleans user-owned schema objects through Ptah’s destructive database-cleanup runtime.
| Flag | Behavior |
|---|---|
--dry-run |
Prints the planned cleanup. |
--auto-approve |
Skips the interactive confirmation, which is otherwise preserved. |
--include |
Full mode only. Keeps objects matched by Atlas resource selectors; dependent cleanup rows such as foreign keys and implicit sequences ride with their table. An owned sequence cannot be selected without its table. |
--exclude |
Full mode only. Subtracts objects matched by Atlas resource selectors. Every independently writer-owned object kind remains selectable; an owned sequence cannot be preserved while its table is selected for removal. |
--format |
Renders Atlas-style templates over the cleanup plan. |
--env |
Reads env.url and format.schema.clean from atlas.hcl. |
The plan reports the object kinds the target dialect’s cleanup really destroys,
so a --dry-run or --format report is not narrower than the apply:
| Dialect | Reported and destroyed |
|---|---|
| PostgreSQL family | Foreign keys, tables, views, materialized views, enum, domain, composite and range types, and functions. PostgreSQL itself also reports standalone sequences, foreign tables, procedures, aggregates, collations, and default privileges. |
| MySQL, MariaDB | Foreign keys, tables, views, stored functions and procedures, events, and MariaDB sequences. |
| SQLite | Tables and views. |
| SQL Server | Foreign keys and tables. Views are not dropped, so they are not reported. |
| ClickHouse | Base tables, views, and materialized views. A materialized view is removed with DROP VIEW, which takes its inner storage table with it; live views and window views are neither read nor dropped. |
Ptah’s own migration revision table is included in that accounting. On
PostgreSQL, MySQL, MariaDB and SQL Server the cleanup destroys it like any other
table, so the plan names it and the operator confirming a destructive run is
confirming the loss of the record of which migrations have been applied. On
SQLite the cleanup keeps it, so it is neither reported nor destroyed. Both
revision-table layouts are covered, Ptah’s schema_migrations and the
Atlas-compatible atlas_schema_revisions, and a revision table configured under
any other name is reported as the ordinary table it is.
Objects that vanish as collateral of a listed drop are not listed separately:
indexes, triggers, non-foreign-key constraints, RLS policies, and comments. The
report order is alphabetical by object kind rather than an execution order. An
unscoped cleanup rebuilds its statements from the live catalog; a scoped
cleanup executes the reported Cmd values in a separate deterministic order
that removes known dependents before their dependencies. PostgreSQL uses live
catalog depth to order dependent views and materialized views of the same kind.
Every PostgreSQL-family target executes the complete scoped plan in one
transaction and retries selected dependencies after a RESTRICT refusal. An
external dependency rolls back earlier selected drops instead of leaving a
partially cleaned schema.
PostgreSQL SERIAL and identity sequences are recognized as implicit table
children, execute after their parent table, and are not reported as forbidden
standalone sequences by the strict CE oracle profile. A selector that tries to
make the owned sequence and its table disagree is refused before mutation.
Function objects and changes expose full declaration Parameters.
PostgreSQL-family scoped drops use RESTRICT, so the database refuses a
selected parent when an unselected view, foreign key, or other catalog
dependency still refers to it. The narrowed command cannot cascade beyond the
objects its plan reports.
Function Cmd values use PostgreSQL identity arguments, so overloaded,
defaulted, and OUT-only functions remain distinct and executable.
Native twin: ptah db drop-all, which drops every schema
object in the database. It registers no --include or --exclude, so the
narrowed cleanup this command performs is reachable only here.
ptah-compat schema test [paths]
Section titled “ptah-compat schema test [paths]”Forwards to ptah schema test.
| Atlas flag | Native equivalent |
|---|---|
-u/--url |
--root-dir. Accepts Go annotations, a SQL or HCL file, or a live database URL. |
--dev-url |
The native throwaway database; an ephemeral SQLite database when omitted. |
--run |
The native case-name filter. |
--var |
Repeatable HCL schema-variable values. An explicit --url keeps these values; a project data.hcl_schema source uses its block-scoped vars. |
| Positional path (optional) | The directory of Ptah-native YAML test cases. |
With --env, schema.src supplies the desired schema URL and dev the dev
database. A source from data.hcl_schema keeps that source’s variable scope,
including an empty scope that excludes run-wide values. Exit codes match the
native runner: 0 when all cases pass, 1 on test failure.
Atlas keeps schema test in its Pro build, so this is a free Ptah capability
rather than an Atlas CE stub.
ptah-compat schema push
Section titled “ptah-compat schema push”Registered but not implemented because the command targets an account-bound
hosted registry protocol. --help reports that the command is not implemented
and exits 0; direct execution reports the same status and exits 1. The open
replacement is the native ptah schema push to any OCI registry.
Related pages
Section titled “Related pages”- Runnable migrate workflows and format template fields: Atlas migrate commands.
- Runnable schema workflows and format template fields: Atlas schema commands.
- Measured compatibility evidence: Conformance.