Skip to content

Operation replay β€” staging allowlist and migration classification ​

Operational notes for .github/workflows/operation-replay.yml ("React to Flutter operation replay"). Written up 2026-08-03 after the lane sat red across six catch-up PRs; everything here is the shape of that failure and how to avoid repeating it.

It is manual-dispatch only ​

The workflow is on: workflow_dispatch. It never runs on push or PR.

That single fact drives everything else: an unclassified migration is invisible until someone runs the lane. It does not turn main red, nobody gets a notification, and by the time it is next dispatched several migrations have accumulated β€” so it fails on whichever it reaches first, gets one entry added, and fails on the next one. Between 2026-08-02 and 2026-08-03 that produced PRs #533, #534, #535, #536, #540 and #544, and it was still red after most of them.

Run it with:

bash
gh workflow run "React to Flutter operation replay" --ref main

The four lists ​

Before mutating the protected mobile-E2E staging backend, the lane requires every migration pending against staging to be classified in one of four heredocs in that YAML:

listmeaning
allowed_migrationsaudited β€” safe to APPLY to staging
ledger_only_migrationsmark-applied only, never executed
staging_excluded_migrationsnever applied to staging
allowed_staging_only_versionsversions allowed to exist on staging with no local file (the opposite direction)

Two things that are easy to get wrong:

  • The first three are not mutually exclusive. ledger_only and staging_excluded refine allowed_migrations β€” they say what to do with a migration the lane may process. An entry appearing in two lists is normal.
  • The lists do not enumerate all ~1600 migrations, and must not. Staging applied the historical ones long ago, so they are never pending and never consulted. Only the pending set matters.

The allowlist is an audit gate, not bookkeeping: a human confirmed the migration is safe to replay against staging. Do not auto-derive it from supabase/migrations/ β€” that deletes the audit and lets an unreviewed migration mutate staging, which is the property the gate exists to protect.

Get the pending set in one query ​

Do this before dispatching, instead of dispatching and reading the error.

The staging project is xucmosnlwhqferhdjnbo (aldilaijan-mobile-e2e-staging). Its ledger is directly queryable:

sql
select version from supabase_migrations.schema_migrations order by version;

Diff that against the repo and against the three classification lists:

bash
git ls-tree -r --name-only origin/main -- supabase/migrations | grep '\.sql$'

On 2026-08-03 this returned 14 pending, 13 already classified, 1 missing β€” and ended the one-at-a-time loop immediately.

The PR-time guard ​

.github/replay-allowlist-guard.mjs runs from the contract lane and fails a PR that adds a migration without classifying it, so discovery happens in the PR that introduces it rather than weeks later on a dispatch.

It detects by added files β€” git diff --name-only --diff-filter=A <base>...HEAD -- supabase/migrations β€” not by timestamp. An earlier version used a high-water mark (newest version mentioned in any list) and could not see a back-dated migration; 20260728160000_valuations_avenue_column.sql sorted below the watermark, slipped past, and blocked the lane within the hour.

contract.yml already checks out with fetch-depth: 0, so the diff works with no workflow change. When no base ref resolves (local run, shallow checkout) the guard falls back to the watermark heuristic and says so in its output, so a weaker check is not mistaken for the strong one.

Run it locally:

bash
GUARD_BASE_REF=origin/main node .github/replay-allowlist-guard.mjs

Adding a new CI workflow ​

Don't, if you can avoid it. .github/ci-budget.json caps max_auto_on_push, and the CI budget guard fails any PR that exceeds it. That cap is deliberate β€” fold new checks into an existing lane instead of raising it. This guard lives in contract for exactly that reason: contract already triggers on supabase/migrations/** and is already budgeted.

Aldilaijan & Khobara Real Estate Platform