The migration log
What ptah migrations log records, what it cannot be used for, and why a rollback no longer erases the fact that a version was applied.
The revision table answers what is applied. It cannot answer what happened: a completed rollback deletes the row, so a database that was on version 43 yesterday and is on 42 today reads exactly like one that never reached 43.
The migration log answers the second question. It is an append-only table
beside the revision table, written by ptah migrations up and ptah migrations down, and read by ptah migrations log.
ptah migrations log --db-url "$DATABASE_URL"STARTED VERSION OPERATION OUTCOME ACTOR SOURCE2026-09-21T06:11:04Z 43 down rolled_back release-bot provided2026-09-21T05:58:12Z 43 up applied release-bot provided2026-09-21T05:57:40Z 43 up failed release-bot providedWhat a row means
Section titled “What a row means”Each attempt writes two entries: one when it starts and one when it ends. The
reader pairs them, so an attempt that never gained an outcome is reported as
undetermined — a run whose process was killed, lost its connection or had its
container stopped says nothing about whether its work landed, and inferring
success from a missing failure would be a claim nobody can support.
The entries are written on the pool connection rather than inside the migration’s transaction, which is the point: a migration that failed and rolled back still leaves the record that it was attempted. A log written inside the transaction would describe only the runs that succeeded.
--json prints the same attempts with the start and finish timestamps, the run
identifier, the checksum of the migration that ran, and the error.
Under tx-mode all the whole batch is one transaction, so every migration in it
gets an entry opened before the transaction starts and they all settle with the
same outcome. Recording one of them applied when a later one rolls the batch
back would describe a database that never existed.
ptah migrations down --plan derives its rollback from the schema difference
instead of running the authored down bodies. It deletes the same revision rows,
so it records the same attempts.
The actor, and what the name is worth
Section titled “The actor, and what the name is worth”--actor names the run. It is unverified by construction — a name on a command
line is what somebody wrote — so the log stores where the name came from beside
it:
| Source | Meaning |
|---|---|
provided |
the caller supplied the name, through --actor or PTAH_ACTOR |
process-user |
nobody supplied one, so the user the process runs as was recorded |
unknown |
neither was available |
Storing an unverified name and an observed one the same way is how a log becomes evidence for something it cannot support.
What this is not
Section titled “What this is not”It is not an audit trail. The table lives in the database it describes and is writable by the account that runs migrations: whoever can apply a migration can edit the record of having done so. Tamper evidence is a different design.
It is not a second source of truth. ptah migrations status remains the
answer to where the database stands, and nothing reads the log to decide the
current version. A reader that had to reconcile two tables to learn that would
be worse off than with no log at all.
It keeps everything. Rows are small and nothing prunes them. A deployment
that needs a retention rule applies its own DELETE.
A destructive clean destroys it. ptah db drop-all names the log table in
the plan it asks you to confirm, beside the revision table, and drops it with
the rest.
Turning it off
Section titled “Turning it off”migration.log: false in the project config keeps the table out of a database
entirely: nothing creates it, and ptah migrations log says logging is off for
the run rather than printing an empty table. The Atlas-compatible revision
format keeps no log whatever the setting says: that contract defines one table,
and a second one Ptah added would appear in a database an Atlas user believes
only Atlas writes. The two are different settings, so the command names the one
that applies.
The table is created by the first entry written, not by the initialization that
creates the revision table. An account that may read and not create can run
ptah migrations log against a database that has no log yet.