```yaml
id: UJ-013
title: Queue selected posts for translation
actors: [administrator]
surface: admin-spa
goal: Administrator queues every selected missing post-language pair exactly once and sees final counts.
trigger: Select posts and target languages, then activate Translate in bulk bar at /wp-admin/admin.php?page=international-press-zone#/translations/posts.
fixtures: [authenticated-admin, source-post-set, active-target-languages, configured-translation-api]
success_state:
  visible: Bulk Translation completion view shows exact queued and failed counts.
  durable: One open job exists per selected missing post-language pair.
  persistence: Fresh job list returns exact pair set and statuses.
source_specs: [admin/src/pages/translate-posts.js, includes/API/TranslateController.php, includes/API/TranslateJobsController.php]
readiness: draft
canonical: false
blockers: []
```

## Path

| Hop | Evidence |
|---|---|
| UI trigger | `admin/src/pages/translate-posts.js:1246` |
| Request construction | `admin/src/pages/translate-posts.js:1346` |
| Endpoint auth and parse | `includes/API/TranslateController.php:148` |
| Authoritative write | `includes/API/TranslateController.php:806` |
| Response | `includes/API/TranslateController.php:533` |
| Terminal render | `admin/src/pages/translate-posts.js:1510` |

## Happy path

### H1
- Setup: Administrator authenticated; selected posts have exact missing target pairs; API configured; no open duplicate jobs.
- Action: Select posts, choose target or all missing languages, then activate bulk `Translate`.
- Request: `POST /wp-json/international-press-zone/v1/translate-async`.
- Response: `200` with `{success:true,jobs:[<job>],already_queued:[]}` for each selected pair.
- Visible: Bulk completion view reports exact total queued, zero failed, and one completed row per pair.
- Durable: Open-job set equals selected missing post-language pair set with no duplicates.
- Fresh read: Jobs GET returns exact pair set, source ownership, and pending or processing statuses.
- Forbidden: No unselected post/language is queued; no source post mutates; no pair is submitted twice.
- Evidence:
  - Setup: `admin/src/pages/translate-posts.js:1281`
  - Action: `admin/src/pages/translate-posts.js:1246`
  - Request: `admin/src/pages/translate-posts.js:1346`
  - Response: `includes/API/TranslateController.php:533`
  - Visible: `admin/src/pages/translate-posts.js:1510`
  - Durable: `includes/API/TranslateController.php:806`
  - Fresh read: `includes/API/TranslateJobsController.php:185`
  - Forbidden: `admin/src/pages/translate-posts.js:1291`

## Alternate and failure paths

### A1
- Setup: Administrator authenticated; selected posts already have every chosen target translation.
- Action: Activate bulk `Translate`.
- Request: N/A — client derives zero missing pairs and rejects before transport.
- Response: N/A — no HTTP request is produced.
- Visible: `None of the selected posts need translation.` renders.
- Durable: Job set and all post/translation state remain unchanged.
- Fresh read: Jobs GET returns no new job for selected pairs.
- Forbidden: No translation request, job insert, post write, or selection outside target occurs.
- Evidence:
  - Setup: `admin/src/pages/translate-posts.js:1281`
  - Action: `admin/src/pages/translate-posts.js:1281`
  - Request: `admin/src/pages/translate-posts.js:1305`
  - Response: `admin/src/pages/translate-posts.js:1305`
  - Visible: `admin/src/pages/translate-posts.js:1306`
  - Durable: `admin/src/pages/translate-posts.js:1305`
  - Fresh read: `includes/API/TranslateJobsController.php:185`
  - Forbidden: `admin/src/pages/translate-posts.js:1305`

## Permissions and boundaries

### P1
- Setup: Actor lacks `manage_options`; selected source posts and targets otherwise valid.
- Action: Submit one async request from intended bulk pair.
- Request: `POST /wp-json/international-press-zone/v1/translate-async`.
- Response: `403` with `{code:"rest_forbidden",message:"Sorry, you are not allowed to do that.",data:{status:403}}`.
- Visible: REST denial exposes no job or protected content.
- Durable: No job or post state changes.
- Fresh read: Protected job list returns permission denial.
- Forbidden: No job insert, backend request, or content disclosure occurs.
- Evidence:
  - Setup: `includes/API/TranslateController.php:961`
  - Action: `includes/API/TranslateController.php:152`
  - Request: `includes/API/TranslateController.php:148`
  - Response: `includes/API/TranslateController.php:963`
  - Visible: `includes/API/TranslateController.php:963`
  - Durable: `includes/API/TranslateController.php:963`
  - Fresh read: `includes/API/TranslateJobsController.php:757`
  - Forbidden: `includes/API/TranslateController.php:963`

## Source specs

- `admin/src/pages/translate-posts.js`
- `includes/API/TranslateController.php`
- `includes/API/TranslateJobsController.php`
