# Make a Database Schema Change in a Rails App as a Team

An agent proposes a Rails email-table migration and its SQL, waits for your team's follow-up approval, then updates application code and tests in a PR.

[Run this on Rush](https://cloud.getrush.ai/run/pair-on-schema-change/rails/), in a Project that has your repository.

## What the agent does

The agent traces the current email behavior before proposing a table. In [rubygems/rubygems.org](https://github.com/rubygems/rubygems.org/blob/HEAD/app/models/user.rb#L3-L22), User includes Clearance authentication and `EmailConfirmable`, clears confirmation state when email fields change, and records update and verification events. [db/schema.rb](https://github.com/rubygems/rubygems.org/blob/HEAD/db/schema.rb#L678-L719) contains `email`, `unconfirmed_email`, confirmation-token fields, `email_confirmed` and several email indexes on users. Moving an address therefore affects sign-in and verification as well as an association. The request is to split email storage into a verified-emails table without treating existing unverified addresses as verified.

Its first turn writes only a proposed additive migration and explains the generated SQL, indexes, foreign key and rollback behavior. [The Gemfile](https://github.com/rubygems/rubygems.org/blob/HEAD/Gemfile#L62-L63) already includes `maintenance_tasks` and `strong_migrations`; [the maintenance-task guidance](https://github.com/rubygems/rubygems.org/blob/HEAD/doc/maintenance-tasks.md#L7-L15) requires safe reruns, preservation of newer writes and a working application during partial backfill. The agent follows those conventions rather than placing a long data rewrite inside the schema migration. It retains the old columns and asks the team to approve the table shape, uniqueness rule, primary-address policy and staged rollout before changing models or queries.

The approval is a follow-up after that turn has finished. Once it arrives, the agent updates the existing authentication and confirmation paths and implements the approved backfill through the repository's maintenance-task mechanism. [EmailConfirmable](https://github.com/rubygems/rubygems.org/blob/HEAD/app/models/concerns/email_confirmable.rb#L49-L64) consumes confirmation tokens under `with_lock`, updates the address and clears token state. The new design must preserve single-use verification and the distinction between a confirmed address and a pending change. A partially backfilled account must still behave according to the approved compatibility plan, rather than silently losing the ability to sign in.

The agent verifies the migration and approved data transition on a disposable Postgres database. It extends `test/models/user_test.rb` and [the existing confirmation concurrency test](https://github.com/rubygems/rubygems.org/blob/HEAD/test/models/email_confirmation_concurrency_test.rb#L20-L43), which already checks competing consumers of the same token. Tests cover case-insensitive lookup, pending-address handling, rerunning the backfill, changes made during processing and denied or expired confirmations. It runs the focused tests and the supported `bin/rails test` and system checks described by CI. The pull request includes the rollout sequence and keeps destructive column removal out of this first change.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "codex",
  "input": "Split users.email storage into a separate verified-emails table through a staged Rails change. First inspect User, EmailConfirmable, authentication lookups, schema/indexes, strong_migrations and maintenance-task guidance. In the first turn write only the proposed additive migration, show its SQL and rollback limits, and explain primary-address, case-insensitive uniqueness, unverified/pending-address and partial-backfill behavior. Retain old columns, do not apply to production, and end the turn waiting for a follow-up approving the design. Only after approval update models, authentication queries, confirmation flows and tests; use the existing MaintenanceTasks mechanism for a rerunnable backfill that preserves newer writes. Verify on disposable Postgres, including concurrent token consumption and partial backfill. Run focused tests and the supported suite, then open a PR with deployment order and evidence. Do not remove old columns or promote unverified addresses.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

The first result is a migration proposal and SQL explanation, with the application code still unchanged. The implementation result after approval is a pull request containing the additive schema change, updated ownership and lookup logic, a maintenance task and regression tests. The description separates schema expansion, data transition and later cleanup so reviewers can evaluate deployment order.

Before merging, the team checks verification semantics, account lookup behavior, uniqueness enforcement and recovery from a partially completed backfill. It also checks that changing an address while the backfill runs cannot restore an older value. The session transcript retains the proposal, approval follow-up and executed checks. It supplies review evidence, not a claim that the change has been applied to a production database.

## Run it on every approved schema-change proposal

Start this request from your schema-design ticket or an internal approval workflow, with the repository and desired transition in the input. Store the returned session identifier beside the proposal. When the first turn finishes, your team reviews its SQL and sends approval or corrections through `POST /o/{org}/sessions/{id}/messages`. That call is for a finished turn; it returns `409 TURN_ACTIVE` while the agent is still working. Use a completion webhook to signal each review point.

## Why run it on Rush?

Email storage sits on an authentication path, so a plausible migration alone is not enough. A Rush sandbox gives the agent a disposable database for migration and concurrency checks, while retaining the commands and their output for the team. Your laptop and production database are not the place where the agent experiments with a partly migrated account.

The turn boundary makes the proposed SQL reviewable before the prompt permits broader edits. The sandbox parks when the turn ends and a follow-up resumes its retained state. Your team can keep the schema decision and later implementation in one transcript, with an explicit approval message rather than a patch that quietly combines table creation, data movement and deletion.

## Frequently asked questions

### Is the approval a special field in the session request?

No, the supplied input instructs the agent to end its proposal turn and wait. The approval is a later message to the same session, not an extra request field or a database deployment authorization.

### Does a verified-emails table mean every existing address is verified?

No, the example already distinguishes confirmed and unconfirmed addresses. The proposal must preserve that distinction rather than changing account trust during a storage refactor.

### Will it backfill inside the migration?

The request follows this repository's existing maintenance-task convention for data movement. It requires safe reruns, partial-progress behavior and preservation of application writes.

### Does this task remove users.email immediately?

No, the first change keeps old columns and documents compatibility during the transition. Destructive cleanup needs a later decision backed by evidence that readers and writers no longer depend on them.
