```yaml
id: UJ-006
title: Persist plugin settings
actors: [administrator]
surface: admin-spa
goal: Administrator changes supported configuration and fresh settings return exact normalized values.
trigger: Change a control at /wp-admin/admin.php?page=international-press-zone#/settings and wait for auto-save.
fixtures: [authenticated-admin, restorable-settings-snapshot]
success_state:
  visible: "Settings saved successfully" renders.
  durable: Corresponding ipz_* WordPress options contain normalized values.
  persistence: Fresh GET /wp-json/international-press-zone/v1/settings returns saved values.
source_specs: [admin/src/pages/settings.js, includes/API/SettingsController.php]
readiness: draft
canonical: false
blockers: []
```

## Path

| Hop | Evidence |
|---|---|
| UI trigger | `admin/src/pages/settings.js:665` |
| Request construction | `admin/src/pages/settings.js:644` |
| Endpoint auth and parse | `includes/API/SettingsController.php:77` |
| Authoritative write | `includes/API/SettingsController.php:322` |
| Response | `includes/API/SettingsController.php:340` |
| Terminal render | `admin/src/pages/settings.js:647` |

## Happy path

### H1
- Setup: Administrator authenticated; current settings snapshot recorded.
- Action: Open Advanced settings and toggle `Debug Mode` once.
- Request: `PUT /wp-json/international-press-zone/v1/settings`.
- Response: `200` with `{success:true,data:{updated_fields:<fields>,count:<count>},message:<count-message>}`.
- Visible: `Settings saved successfully` renders; Debug Mode control reflects submitted state.
- Durable: WordPress option `presszone_international_debug_mode` equals submitted boolean; unrelated options equal snapshot.
- Fresh read: `GET /wp-json/international-press-zone/v1/settings` returns `200` with `data.debug_mode` equal submitted state.
- Forbidden: API key, URL mode, cache, language, and translation state remain unchanged.
- Evidence:
  - Setup: `includes/API/SettingsController.php:158`
  - Action: `admin/src/pages/settings.js:538`
  - Request: `admin/src/pages/settings.js:644`
  - Response: `includes/API/SettingsController.php:340`
  - Visible: `admin/src/pages/settings.js:647`
  - Durable: `includes/API/SettingsController.php:322`
  - Fresh read: `includes/API/SettingsController.php:158`
  - Forbidden: `includes/API/SettingsController.php:217`

## Alternate and failure paths

### A1
- Setup: Administrator authenticated; current `batch_size` differs from the schema boundary.
- Action: Submit `batch_size` above the schema-declared maximum through settings request.
- Request: `PUT /wp-json/international-press-zone/v1/settings`.
- Response: `400` with `{code:"rest_invalid_param",message:"Invalid parameter(s): batch_size",data:{status:400,params:{batch_size:<validation-message>},details:<validation-details>}}`.
- Visible: `Invalid parameter(s): batch_size` renders and settings remain editable.
- Durable: WordPress option `ipz_batch_size` retains snapshot value.
- Fresh read: `GET /wp-json/international-press-zone/v1/settings` returns `200` with original `batch_size`.
- Forbidden: No out-of-range `batch_size` or unrelated option is persisted.
- Evidence:
  - Setup: `includes/API/SettingsController.php:567-573`
  - Action: `admin/src/pages/settings.js:497-506`
  - Request: `includes/API/SettingsController.php:78-87`
  - Response: WordPress core `rest_validate_request_arg()` schema validation (min/max) rejects the request before `updateSettings()` runs.
  - Durable: `includes/API/SettingsController.php:261-265`
  - Fresh read: `includes/API/SettingsController.php:136-165`
  - Forbidden: `includes/API/SettingsController.php:261-265`

## Permissions and boundaries

### P1
- Setup: Actor lacks `manage_options`.
- Action: Submit settings update.
- Request: `PUT /wp-json/international-press-zone/v1/settings`.
- Response: `403` with `{code:"rest_forbidden",message:"You do not have permission to manage settings.",data:{status:403}}`.
- Visible: REST denial exposes no saved settings or secrets.
- Durable: All `ipz_*` options remain unchanged.
- Fresh read: `GET /wp-json/international-press-zone/v1/settings` returns the same `403` denial.
- Forbidden: No option changes; API key value is not disclosed.
- Evidence:
  - Setup: `includes/API/SettingsController.php:580`
  - Action: `includes/API/SettingsController.php:79`
  - Request: `includes/API/SettingsController.php:77`
  - Response: `includes/API/SettingsController.php:581`
  - Visible: `includes/API/SettingsController.php:581`
  - Durable: `includes/API/SettingsController.php:580`
  - Fresh read: `includes/API/SettingsController.php:66`
  - Forbidden: `includes/API/SettingsController.php:580`

## Source specs

- `admin/src/pages/settings.js`
- `includes/API/SettingsController.php`
