# erun migration-generation case study

Verified October 2, 2026 against public erun PR
[#2731](https://github.com/sophium/erun/pull/2731), head
`f1f9d73269cdbfaef7d0eee7dd6df2d0c9536017`.

The `erun-db/` directory contains the original `atlas.hcl`, all 80 SQL schema
sources, and all 39 SQL migrations plus `atlas.sum`. Their source bytes are
bound by `upstream-files.json`. Other application files are not needed here.
The erun inputs, probe, behavioral assertions, and catalog comparison are
reproduced under the [MIT license](ERUN-LICENSE.txt).

## Versions

- Released Ptah Compat 0.11.4, commit
  `e230381e1cd3241d65c62df57b8b583299a54e74`, official Darwin/arm64 archive.
  Archive SHA-256:
  `1d72b8f801d570f5d0a74e775c43f7a63016a3759cc34718057caaf94a2fb230`,
  verified against the release's `checksums.txt`.
- Ptah Compat edge, built locally with Go 1.27.1 from master commit
  `8f4d1301f9d5caf181c95fc4ec120e61d5a22f32`. This is the merge of
  [conditional role bootstrap support](https://github.com/stokaro/ptah/pull/3997),
  not a released 0.11.4 capability. The binary hash is in `verified.json`.
- PostgreSQL 18.6. The existing `docker://postgres/18/dev?search_path=public`
  configuration resolved to
  `postgres@sha256:1957b2ff3137e4ef7f3bc813e74fff50b1e1ffddc85c8b9d6f14ade972be8687`
  during this run. The dedicated target server used that digest too.

The article uses `atlas`, the installation name proposed by erun's PR. Our
local measurements invoked the corresponding `ptah-compat` executable directly;
the command arguments and compatibility interface are the same. `commands.json`
records the actual invocations, working directories, exit codes, and timestamps.

## Setup

Use an isolated PostgreSQL server. The historical migrations create
cluster-wide roles. A fresh database on an unrelated server is not a substitute
for a disposable server when replaying that history.

The recorded run used the explicit Docker context `remote-dev-container`:

```console
docker --context remote-dev-container run -d --name ptah-blog-erun-lab \
  -e POSTGRES_USER=erun -e POSTGRES_PASSWORD="$LAB_DB_PASSWORD" \
  -p 127.0.0.1:55439:5432 \
  postgres@sha256:1957b2ff3137e4ef7f3bc813e74fff50b1e1ffddc85c8b9d6f14ade972be8687
```

For a remote host, forward its loopback port with SSH. The measured clients
used local port 55439 through such a tunnel. Set `DOCKER_CONTEXT` explicitly
for the compatibility binary's disposable `docker://` dev servers as well.
The target's lab superuser can create roles; this is not an application login.

Create `erun_stable`, `erun_declared`, and `erun_edge` databases on the target.
Set `DATABASE_URL` to the chosen target URL, for example
`postgres://erun:<password>@localhost:55439/erun_stable?sslmode=disable`.
This lab connection used SSH forwarding; the URL is not a production TLS recipe.

Work in separate copies of `erun-db/` for the released and edge runs. Preserve
the supplied directory as the baseline. Install the selected `ptah-compat`
binary as `atlas` on `PATH` before each run.

## Released migration generation

From the stable working copy:

```console
atlas version
atlas migrate apply --env default --url "$DATABASE_URL"
atlas migrate diff unchanged --env default
```

All 39 historical migrations apply. The first diff leaves all 40 history and
checksum files unchanged. erun's own independent `catalog.sql` comparison
matches a database created by executing the 80 sources in `atlas.hcl` order.
The two baseline catalog outputs are retained in `measured/`.

Copy `probe.sql` to `schema/ptah_probe.sql` in this working copy, then add
`"file://schema/ptah_probe.sql",` at the end of `atlas.hcl`'s `src` list. This
adds the same temporary regression object as the upstream PR. Run:

```console
atlas migrate diff generated_objects --env default
atlas migrate apply --env default --url "$DATABASE_URL"
psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 -f ../behavior.sql
atlas migrate diff settled --env default
```

Adjust the path to `behavior.sql` to its location beside this README. The
recorded fixture used psql inside the target container rather than a host
installation. `generated-objects.sql` is the generated file, copied without
edits. The original SQL migrations stay byte-identical; generation extends
`atlas.sum` normally. The repeated diff changes neither files nor checksums.

For the independent comparison, apply `probe.sql` to the declared database,
then run `catalog.sql` on both databases. The two generated-state catalog
outputs must match. `behavior.sql` additionally checks trigger execution,
forced RLS, tenant isolation, cross-tenant write refusal, column-grant scope,
and operations access. Its data changes are rolled back.

## Edge conditional roles

Use the separate edge working copy and target `erun_edge`:

```console
atlas version
atlas migrate apply --env default --url "$DATABASE_URL"
atlas migrate diff unchanged --env default
```

The unchanged desired state again produces no migration and preserves all 40
history/checksum files. Append `new-role.sql` to `schema/roles.sql` in that copy:

```console
atlas migrate diff add_auditor --env default
atlas migrate apply --env default --url "$DATABASE_URL"
psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 -f ../role-check.sql
atlas migrate diff settled --env default
```

`generated-role.sql` is the unedited generated migration. The catalog check
shows `erun_auditor` as NOLOGIN, with public-schema usage and SELECT but no
INSERT on `audit_events`. An independent SQL assertion checks those properties.
The existing 39 migration files remain byte-identical. The last diff adds no
files and does not change `atlas.sum`.

This validates role creation and privileges, not an auditor application
rollout. The existing RLS policies do not name the new role; a SELECT grant
alone does not grant visibility into tenant rows. No login credentials or
role membership were added.

## Evidence and scope

`measured/commands.json` retains combined stdout/stderr hashes, timestamps,
actual arguments, and exit codes. Published output strips ANSI escapes and
trailing whitespace, replaces the local scratch path with `/work`, and redacts
the disposable target password. `verified.json` binds the published inputs,
outputs, binary identity, and article command blocks.

The article reports these fresh database examples. The broader API suite,
image-platform checks, and Atlas-to-Ptah handover reported in erun's PR are
not claimed as additional article runs. The upstream proposal remains pinned
to 0.11.4; the edge test does not change its source or deployment images.

After verification, the dedicated target container, its anonymous volume,
and the SSH tunnel were removed. Ptah's temporary dev containers were cleaned
up by each command. The PostgreSQL image existed before these tests and was
retained. Remove only your own reproduction container when finished:

```console
docker --context remote-dev-container rm -fv ptah-blog-erun-lab
```
