# Releases and migration safety for agents

Read rehearsal evidence, confirm parked releases, and steer the release safety dial. This page is written for an AI agent. It extends the deployment protocol at `https://app.tokay.io/llms.txt`. Everything here is an escape hatch. The happy path needs none of it, so act on an explicit user request or a named readiness action, not on your own initiative.

## The release model

- Tokay rehearses every managed database migration against a disposable clone of the production data before anything touches production. The evidence from that rehearsal, not the SQL text, is what release policy judges.
- Two evidence channels ride each release execution. `impactAssessment` carries observed facts, including the `planRollup` with schema diffs and the policy result. `releaseAnalysis` carries AI verdicts, `liveSafe` (can the currently live code survive the migration), `deployOk` (will the new code work after it), and `dataSafety` (`safe`, `risky`, or `uncertain`).
- `releaseSafetyMode` decides how much of that authority is automatic. `MANUAL` parks everything, including rollbacks. `STRICT` applies only deterministically additive changes automatically. `NORMAL`, the default, adds changes the AI judged safe. `UNRESTRICTED` applies everything automatically, still snapshotting managed databases first. Deploy calls accept an optional `releaseSafetyMode` where the strictest value wins, so a request can narrow authority for one deploy but never widen it.
- A parked release is not a failure. `deployReadiness.action: CONFIRM_RELEASE` means evidence is waiting for a human decision. Your job is to read it, summarize it faithfully, and confirm only after the user agrees.
- External databases that Tokay does not host get no rehearsal and no snapshot, and park under every mode except `UNRESTRICTED`.

## What to call

| To | Use |
|---|---|
| Detect a park | `deployReadiness { action targetDeployment { id } }` |
| Read the evidence | `targetDeployment.releaseExecutions` |
| Apply the parked plan | `confirmDeploymentRelease` |
| Change the dial | `updateProject` / `updateService` patch `releaseSafetyMode` |
| Review a proposed migration command | `service.serviceReleaseActionOverrides`, `updateServiceReleaseActionOverride` |
| Recover a maintenance hold | `recoverServiceMaintenanceHold` |

## Read a parked release

When `deployReadiness.action` is `CONFIRM_RELEASE`, read `deployReadiness.targetDeployment`. Do not assume the newest deployment row is the parked one.

```
query ReleaseConfirmation($serviceId: ID!) {
  service(id: $serviceId) {
    deployReadiness {
      state reason action message
      targetDeployment {
        id deployState deployWaitReason releaseSafetyMode releaseStrategy
        releaseExecutions(orderBy: ACTION_INDEX_ASC) {
          nodes {
            id actionName command releaseExecutionPhase releaseExecutionState
            impactAssessment releaseAnalysis
            projectResource { id resourceType runtimeName }
          }
        }
      }
    }
  }
}
```

`impactAssessment` and `releaseAnalysis` are JSON with camelCase keys. Each database's final `REHEARSAL` row carries `impactAssessment.planRollup` with `diffAtCutover` and `diffFinal` schema diffs (`changes` entries like `{"type": "ADD_COLUMN", ...}`) and the `policy` object. `policy.reasons` is an array of `{reason, message}` objects, such as `{"reason": "MANUAL_MODE", "message": "This project applies release actions only after explicit confirmation."}`. Other reason codes include `DATA_SAFETY_RISKY`, `EXTERNAL_DATABASE_UNVERIFIED`, and `HARD_STOP_EXCEEDS_AUTO_WINDOW`. Summarize the reasons, the diffs, and the `releaseAnalysis` verdicts to the user before confirming.

## Confirm

```
mutation Confirm($deployment: ID!) {
  confirmDeploymentRelease(input: { deployment: $deployment }) {
    deployment { id deployState }
  }
}
```

Confirming runs the rehearsed plan against production. Tokay snapshots each managed database immediately before mutating it. After confirming, poll the service back to `deployState: DEPLOYED` with `deployReadiness.state: UP_TO_DATE`. A release that applied automatically shows the same evidence trail afterward, a `REHEARSAL` row and then a `PRODUCTION` row per action, so you can always answer what ran and why it was allowed.

## The safety dial

`releaseSafetyMode` lives on the project (`project.releaseSafetyMode`) and services inherit it. A service can override with its own value, where null means inherit. Change it through the generated update mutations, for example `updateProject(input: { id: $id, patch: { releaseSafetyMode: MANUAL } })`. Under `NORMAL`, do not expect every statement that looks destructive to park. The rehearsal runs against real data, and a drop that loses nothing observable can be judged safe and apply on its own. Change the dial only when the user asks for it. If the user wants a guaranteed gate, set `MANUAL` and treat `CONFIRM_RELEASE` as the routine path. `wontRunBypass: true` on a deploy call overrides only an AI "new code won't work" prediction. Rehearsal failures always block.

## Migration command overrides

`UNCONFIRMED_RELEASE_ACTION` as a deploy blocker means Tokay found migration machinery it cannot run automatically and proposed a command. List the service's `serviceReleaseActionOverrides` and review `commandArgv`, `workingDirectory`, and `releaseActionTiming`. Enable with `updateServiceReleaseActionOverride(input: { id: $id, patch: { enabled: true } })`, editing `commandArgv` in the same patch when the proposal is wrong, or dismiss with `patch: { dismissedAt: "<now ISO8601>" }`. Never enable a command you have not shown the user.

## Maintenance holds

`deployReadiness.action: RECOVER_MAINTENANCE_HOLD` means a release left the runtime in a state Tokay will not touch without a human assertion, and the app may be serving a maintenance response. After the user verifies the runtime target is safe, call `recoverServiceMaintenanceHold(input: { service: $service, safetyAssertion: "..." })` with a sentence recording what was verified, then poll readiness.

## Error shapes

| You see | It means | Do this |
|---|---|---|
| `deployReadiness.action: CONFIRM_RELEASE` | Evidence is parked for a decision | Read `targetDeployment`, summarize, confirm only with user agreement |
| `deployWaitReason: AWAITING_RELEASE_CONFIRMATION` | Same park, seen from the deployment row | Follow the readiness action, not this field alone |
| A rollback parks at `CONFIRM_RELEASE` | The project is in `MANUAL` mode | Confirm it like any release. It runs no migrations |
| `releaseExecutionState: FAILED` on a `REHEARSAL` row | The migration failed against the clone | Fix the migration and push again. Rehearsal failures never reach production |
| `policy.reasons` contains `EXTERNAL_DATABASE_UNVERIFIED` | Tokay cannot rehearse a database it does not host | Warn the user there is no rehearsal or snapshot behind this confirm |
