```yaml
id: UJ-017
title: Migrate WPML data
actors: [administrator]
surface: admin-spa
goal: Administrator migrates WPML languages, translations, and strings into International Press Zone and verifies exact counts.
trigger: Activate Start Migration at /wp-admin/admin.php?page=international-press-zone#/migration after successful preflight and backup.
fixtures: [authenticated-admin, wpml-migration-dataset, migration-backup]
success_state:
  visible: "Migration Successful!" renders with exact processed counts and verification.
  durable: IPZ language, translation, and string tables contain mapped WPML dataset and completed progress.
  persistence: Fresh migration status and verification return completed exact counts.
source_specs: [admin/src/pages/migration.js, includes/API/MigrationRestController.php, includes/Core/WPMLMigrator.php]
readiness: draft
canonical: false
blockers: []
```

## Path

| Hop | Evidence |
|---|---|
| UI trigger | `admin/src/pages/migration.js:231` |
| Request construction | `admin/src/pages/migration.js:362` |
| Endpoint auth and parse | `includes/API/MigrationRestController.php:92` |
| Authoritative write | `includes/Core/WPMLMigrator.php:394` |
| Response | `includes/API/MigrationRestController.php:199` |
| Terminal render | `admin/src/pages/migration.js:398` |

## Happy path

### H1
- Setup: Administrator authenticated; WPML fixture has named languages, translation groups, and strings; preflight and backup are complete.
- Action: Activate `Start Migration` and wait while UI processes batches to completion.
- Request: `POST /wp-json/international-press-zone/v1/mpz/v1/migration/start`.
- Response: `200` with `{success:true,<migration-start-state>}`.
- Visible: `Migration Successful!` renders with exact migrated and skipped counts; verification reports no mismatch.
- Durable: Target language, translation, and string tables equal mapped fixture; migration progress has `stage:"completed"`, completion timestamp, and exact processed counts.
- Fresh read: `GET /wp-json/international-press-zone/v1/mpz/v1/migration/status` and verify endpoint return `200` with completed exact counts.
- Forbidden: WPML source tables remain unchanged; no duplicate mapped row, unrelated option, content, license, or account state changes.
- Evidence:
  - Setup: `includes/API/MigrationRestController.php:70`
  - Action: `admin/src/pages/migration.js:231`
  - Request: `admin/src/pages/migration.js:362`
  - Response: `includes/API/MigrationRestController.php:199`
  - Visible: `admin/src/pages/migration.js:398`
  - Durable: `includes/Core/WPMLMigrator.php:599`
  - Fresh read: `includes/API/MigrationRestController.php:122`
  - Forbidden: `includes/Core/WPMLMigrator.php:394`

## Alternate and failure paths

### A1
- Setup: Administrator authenticated; preflight contains one failed critical check.
- Action: Attempt to start migration.
- Request: N/A — UI keeps Start Migration unavailable after failed preflight.
- Response: N/A — no HTTP migration-start request is produced.
- Visible: Failed critical preflight check renders and migration cannot start.
- Durable: Target tables and migration progress remain unchanged.
- Fresh read: Migration status GET returns original idle state.
- Forbidden: No backup, migration batch, target write, or WPML source mutation occurs.
- Evidence:
  - Setup: `includes/API/MigrationRestController.php:327`
  - Action: `admin/src/pages/migration.js:231`
  - Request: `admin/src/pages/migration.js:226`
  - Response: `admin/src/pages/migration.js:226`
  - Visible: `admin/src/pages/migration.js:221`
  - Durable: `includes/API/MigrationRestController.php:327`
  - Fresh read: `includes/API/MigrationRestController.php:122`
  - Forbidden: `includes/API/MigrationRestController.php:327`

## Permissions and boundaries

### P1
- Setup: Actor lacks `manage_options`; valid migration fixture exists.
- Action: Submit migration-start request.
- Request: `POST /wp-json/international-press-zone/v1/mpz/v1/migration/start`.
- Response: `403` with `{code:"rest_forbidden",message:"Sorry, you are not allowed to access this endpoint.",data:{status:403}}`.
- Visible: REST denial exposes no migration data or backup path.
- Durable: Source, target, backup, and progress state remain unchanged.
- Fresh read: Protected migration status returns same `403` denial.
- Forbidden: No backup, batch, target write, or source disclosure occurs.
- Evidence:
  - Setup: `includes/API/MigrationRestController.php:300`
  - Action: `includes/API/MigrationRestController.php:98`
  - Request: `includes/API/MigrationRestController.php:92`
  - Response: `includes/API/MigrationRestController.php:301`
  - Visible: `includes/API/MigrationRestController.php:301`
  - Durable: `includes/API/MigrationRestController.php:300`
  - Fresh read: `includes/API/MigrationRestController.php:122`
  - Forbidden: `includes/API/MigrationRestController.php:300`

## Source specs

- `admin/src/pages/migration.js`
- `includes/API/MigrationRestController.php`
- `includes/Core/WPMLMigrator.php`
