# Rollback procedure — International Press Zone

Scope: restoring the last known-good plugin version after a failed rollout of a newer release, without corrupting schema or content. Proven for launch candidate `0.9.84` rolling back to `0.9.74` (see the PG-36 receipt in `receipts/`).

## Rollback artifact

Every rollout MUST keep the previous release ZIP and its SHA-256 available before upgrading. For the current launch pair:

| Role | Artifact | SHA-256 |
|---|---|---|
| Launch candidate | `international-press-zone-0.9.84.zip` | `507f747aac02e93df9e8da6007d12ff01d6103454bc3c89e543e622755e40c22` |
| Last known-good (rollback target) | `international-press-zone-0.9.74.zip` | `5720476d82f96796de9d639d61195745e7f528916ff8ca62f3776499962c5716` |

Verify the artifact before use: `sha256sum <zip>` must match the recorded value exactly. Never roll back with an unverified archive.

## Procedure (wp-admin, no shell required)

1. Confirm the rollback target ZIP's SHA-256 (above).
2. In wp-admin go to **Plugins → Add New → Upload Plugin**, choose the last known-good ZIP, and click **Install Now**.
3. WordPress detects the same plugin slug and shows **"This plugin is already installed."** with a version comparison. Click the **"Replace current with uploaded"** link. (It is a link, not a button.)
4. **Wait for the "Plugin downgraded successfully." confirmation page before navigating anywhere.** The file swap is not atomic: loading wp-admin pages mid-swap can hit a mixed plugin tree and show a transient "critical error" (HTTP 500). That transient error does not mean the rollback failed — wait for the confirmation, then reload. (The PG-36 proof reproduced exactly this transient during the swap window and confirmed the site is healthy immediately after the confirmation page renders.)
5. If an **Activate Plugin** link is shown afterwards, click it; otherwise the plugin remains active through the replacement.
6. On the Plugins screen, confirm the row shows the rollback version and an active **Deactivate** link.
7. Open the plugin admin page (`admin.php?page=international-press-zone`) and confirm the dashboard loads without errors.
8. Spot-check preserved data: plugin settings, workflow states, and recently edited content.

## Data-safety notes

- The replace flow swaps only the plugin files; the database is untouched by the swap itself. The PG-36 proof seeded a published post, plugin options (including `ipz_feature_flags`), and an `ipz_workflow_states` row under `0.9.84`; after rollback to `0.9.74` it verified the post title/content and option values byte-exact and the seeded workflow row still present (existence of the seeded translation ID, not a field-by-field value comparison), with the admin SPA working and zero PHP/JS errors.
- Forward-only schema caution: a future release that alters table schemas incompatibly invalidates this proof for that pair. Re-run the reversed upgrade spec for each new launch pair before relying on rollback (see below).

## Re-proving rollback for a new version pair

The rollback proof is the upgrade spec run in reverse — swap the two ZIP/version pairs so the candidate installs first and the last-known-good replaces it. Run it exactly like the forward upgrade proof documented in `tests/e2e/UPGRADE.md`: the WordPress stack is `tests/e2e/remote-stack.sh` (started as the `e2e-remote` server command on a sanctioned buildbox) and Playwright runs from `tests/e2e/`. Only the env values differ from the forward run:

```
# via e2e-remote, from tests/e2e/ (see UPGRADE.md for the full invocation):
e2e-remote --server 'bash remote-stack.sh' --wait-port 8080 \
  --env IPZ_STACK_NO_PLUGIN_MOUNT=1 \
  --env IPZ_OLD_ZIP_PATH=<candidate zip>       --env IPZ_OLD_VERSION=<candidate version> \
  --env IPZ_ZIP_PATH=<last-known-good zip>     --env IPZ_EXPECTED_VERSION=<last-known-good version> \
  -- bash -c 'npm ci --no-audit --no-fund && npx playwright test upgrade-zip.spec.js --project=chromium'
```

A pass proves: candidate installs and activates, live-like data seeded under the candidate survives the replacement, the older version activates cleanly on the newer version's database, and no errors surface.
