# Reproduce the Ptah 0.8.0 examples

The post uses the official Ptah 0.8.0 release binaries from
<https://github.com/stokaro/ptah/releases/tag/v0.8.0>. Both examples ran on
September 22, 2026 (UTC) with the linux/amd64 `ptah` inside a disposable
PostgreSQL 18.6 container on the Docker context `remote-dev-container`. The
release example was repeated on SQLite with the darwin/arm64 binary.
`verified.json` records the archive and binary checksums, the image digest,
and the hash of every file in this directory.

`DATABASE_URL` is the connection URL of an empty PostgreSQL database you can
discard. In the recorded run, `ptah` and `psql` ran inside the database
container, and `psql` connected over the local socket as
`psql -U ptah -d <database>`. `commands.json` has the exact argument list,
exit code, start time and elapsed time of every command. The URLs in it carry
a test-only credential. Elapsed times include starting `docker exec` over SSH
and are not statement timings.

## online: a constraint generated with `diff.online_alter`

Run these from `online/`. `ptah.yaml` sets `diff.online_alter: true`.
`schema.sql` is the desired schema: the `accounts` table plus the
`accounts_plan_known` check.

Start with only the `1790100000_create_accounts` pair in `migrations/`. Hash it
and apply it:

```console
ptah migrations hash --dir migrations
ptah migrations up --db-url "$DATABASE_URL" --migrations-dir migrations --verify-sum
```

Generate the change, then hash the directory again. `migrations generate`
writes the migration pair but does not update `ptah.sum`:

```console
ptah migrations generate --db-url "$DATABASE_URL" --schema-file schema.sql --migrations-dir migrations --name plan_known
ptah migrations hash --dir migrations
```

The version in the file name is the time of the run. The recorded run wrote
`1790115827_plan_known`. The generator ends the file without a final newline,
and the article's example check compares whole lines, so
`measured/1790115827_plan_known.up.sql` is the up file with a final newline
added. `verified.json` records the hash of both.

Running the same `generate` from a directory without `ptah.yaml` wrote
`measured/without-online-alter.up.sql`: one `ADD CONSTRAINT`, in an ordinary
transactional migration with `lock_timeout=3s` and `statement_timeout=30s`.

Lint the new migration with the default rules, then with the online mode:

```console
ptah migrations lint --dir migrations --dialect postgres --latest 1
ptah migrations lint --dir migrations --dialect postgres --latest 1 --config online-lint.yaml
```

Both exit 0. Both report `PG305` as a warning on the `NOT VALID` line. The
online mode reports no `ON101` finding.

A lock timeout is refused for this file:

```console
ptah migrations up --db-url "$DATABASE_URL" --migrations-dir migrations --verify-sum --lock-timeout 3s
```

It exits 2. `measured/lock-timeout-refused.txt` is the last line of
`measured/up-lock-timeout.stderr.txt`. The migration log of this database
records the attempt as `failed` (`measured/log.txt`).

To apply under the online mode, `migrations up` reads `.ptah-lint.yaml` from
the migrations directory. The recorded run copied `migrations/` to another
directory and saved `online-lint.yaml` in the copy as `.ptah-lint.yaml`. From
that copy, `migrations up --verify-sum --dry-run` exited 2 without
`--lock-timeout`, because the mode requires one on PostgreSQL. It exited 2 with
`--lock-timeout 3s`, because the file refuses one. It exited 0 with
`--tx-mode none`. The `online-mode-up*` files hold the output.

### The lock queue

Use three terminals. In the first, hold the table for ten seconds:

```console
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f blocker.sql
```

About two seconds later, apply the migration in the second:

```console
ptah migrations up --db-url "$DATABASE_URL" --migrations-dir migrations --verify-sum
```

About three seconds after that, in the third, list the sessions waiting for a
lock, then run a read with a two-second lock timeout:

```console
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f waiting.sql
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f reader.sql
```

In the recorded run, `reader.sql` returned at once before the migration
started (`measured/reader-before.txt`). While the migration waited,
`waiting.sql` listed the `ALTER TABLE ... ADD CONSTRAINT` waiting on a lock,
and `reader.sql` exited 3 with `canceling statement due to lock timeout`.
Ptah logged the start of the migration at 22:24:02.725Z and its end at
22:24:10.671Z, after the blocking transaction committed
(`measured/up-behind-blocker.stderr.txt`).

Then check the result:

```console
ptah schema compare --db-url "$DATABASE_URL" --schema-file schema.sql --exit-code
ptah migrations status --db-url "$DATABASE_URL" --migrations-dir migrations --verify-sum --exit-code
```

Both exit 0. `measured/constraint.txt` shows `accounts_plan_known` validated
in `pg_constraint`.

## release: `db verify` and the migration log

Run these from `release/`. `1790100060_add_tier` is the release. Its backfill
misses the `trial` plan on purpose. `existing-accounts.sql` stands for rows the
application wrote before the release.

```console
ptah migrations hash --dir migrations
ptah migrations up --db-url "$DATABASE_URL" --migrations-dir migrations --verify-sum --to-version 1790100000 --actor release-bot
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f existing-accounts.sql
ptah migrations up --db-url "$DATABASE_URL" --migrations-dir migrations --verify-sum --actor release-bot
```

Structure and history both pass. Each command exits 0:

```console
ptah schema compare --db-url "$DATABASE_URL" --schema-file schema.sql --exit-code
ptah migrations status --db-url "$DATABASE_URL" --migrations-dir migrations --verify-sum --exit-code
```

The release checks do not:

```console
ptah db verify --db-url "$DATABASE_URL" --checks release-checks/
```

It exits 1 (`measured/verify.txt`; `measured/verify.json` is the same run with
`--format json`). An empty directory passed to `--checks` printed
`Verdict: not verified (no assertions found)` and also exited 1
(`measured/verify-empty.txt`).

Roll the release back and read the log:

```console
ptah migrations down --db-url "$DATABASE_URL" --migrations-dir migrations --verify-sum --target 1790100000 --confirm --actor release-bot
ptah migrations log --db-url "$DATABASE_URL"
```

`measured/status-after-down.txt` reports one pending migration.
`measured/log.txt` and `measured/log.json` show both attempts on
`1790100060`.

### On SQLite

The same files run on SQLite. Use `sqlite://app.db` as the URL, and load the
rows with `sqlite3 app.db ".read existing-accounts.sql"`. `measured/sqlite/`
holds the compare, verify and log output of that run. Its verification report
is byte-identical to the PostgreSQL one.

## What the recorded runs do not establish

The release example held three rows, and the online example held none. No
application workload ran. The lock-queue measurement used one blocking
transaction and one read. Nothing here measures how long a validation scan
takes on a large table, replication, or the MySQL and MariaDB form of
`diff.online_alter`.
