# Simular y después confirmar

> Cómo la simulación, el motivo y la versión esperada hacen que cada escritura en OpenWAF se pueda revisar y no pise cambios ajenos.

Una escritura de configuración en OpenWAF lleva, además de sus datos, tres cosas: un **motivo**, una marca opcional de **simulación** y la **versión esperada** de la zona. Todas las interfaces las usan igual.

## El motivo

Toda escritura de configuración necesita un motivo de 1 a 500 caracteres (sin contar los espacios de los extremos), sin saltos de línea ni otros caracteres de control. Se guarda en la versión y en la auditoría. Sin él, la API responde `422 reason_required`.

La única excepción es dar de alta una zona: la API la acepta sin motivo y guarda `alta de la zona`. La CLI pide `--reason` igualmente.

## La simulación

Una simulación ejecuta la escritura completa, con la validación, la instantánea nueva y la configuración de cada nodo de borde que sirve la zona, dentro de una transacción, y después la deshace. Falla exactamente donde fallaría la escritura real y no toca ni el DNS ni el borde. La respuesta es el plan:

```json
{
  "dry_run": true,
  "from_version": 7,
  "to_version": 8,
  "diff": {
    "from": 7,
    "to": 8,
    "changes": [
      {"path": "settings.hsts.max_age", "op": "replace", "from": 0, "to": 300}
    ]
  },
  "warnings": []
}
```

Cada cambio tiene una ruta (`path`), una operación (`op`: `add`, `remove` o `replace`) y los valores de antes y de después. Las listas se identifican por identidad, no por posición: `rules[id=12]`, `records[id=…]`, `certificates[hostname=…]`. `warnings` enumera lo que la escritura dejaría fuera, como un sitio que no se ha podido construir. Si `to_version` es igual a `from_version` y no hay diferencias, la escritura no cambiaría nada.

## La versión esperada

La confirmación lleva la versión contra la que simulaste. Si la zona sigue en esa versión, la escritura se aplica y crea la siguiente. Si alguien ha cambiado la zona entretanto, no se aplica nada y la API responde:

```json
{"error": {"code": "version_conflict", "message": "the configuration changed: the current version is 9"}, "current_version": 9}
```

Mira qué ha cambiado (`zones diff`, **Cambios → Historial**), vuelve a simular y revisa el plan nuevo antes de confirmar. Un conflicto de versión nunca aplica un cambio a medias.

## En cada interfaz

**Panel**

Cada editor abre un diálogo con la **Vista previa** del cambio y el campo **Motivo del cambio**; **Aplicar el cambio** lo envía con la versión de la vista previa. Si hay un conflicto, el panel recarga la zona, vuelve a simular tu cambio y te pide que revises la vista previa nueva.
**CLI**

Toda orden que cambia algo exige `--reason`. La CLI simula primero con la versión actual de la zona, enseña las diferencias y pregunta `¿Aplicar? [s/N]`; después aplica con **la misma** versión esperada. `--dry-run` se queda en la simulación y `--yes` no pregunta (obligatorio sin terminal, por ejemplo en CI). Un conflicto termina con el código de salida 3.

```sh
openwaf-cloud ctl zones set example.com hsts.max_age=300 --reason "Activar HSTS" --dry-run
openwaf-cloud ctl zones set example.com hsts.max_age=300 --reason "Activar HSTS" --yes --wait
```
**API**

| Cabecera o parámetro | Regla |
|---|---|
| `X-Change-Reason` | Obligatoria, de 1 a 500 caracteres. |
| `If-Match: "<versión>"` | La versión esperada. Opcional en la mayoría de las escrituras y obligatoria al volver a una versión (sin ella, `428 if_match_required`). |
| `?dry_run=1` | Simular: responde el plan con `200` y no cambia nada. |

Una escritura correcta devuelve la versión nueva en `X-Config-Version` y en `ETag`. Las lecturas de una zona y de sus ajustes de borde devuelven la versión actual en `ETag`.

```sh
curl -sS -X PATCH "https://dash.openwaf.dev/v1/orgs/$ORG/zones/$ZONE/edge?dry_run=1" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "X-Change-Reason: Activar HSTS" \
  -H 'If-Match: "7"' \
  -H "Content-Type: application/json" \
  -d '{"hsts": {"max_age": 300}}'
```

Repite la petición sin `?dry_run=1` y con el mismo `If-Match` para aplicarla.
**MCP**

Cada herramienta de escritura recibe `reason`, `confirm` y `expected_version`. Con `confirm` a false (el valor por defecto) solo simula y devuelve el plan y `current_version`; tu asistente debe enseñarte las diferencias. Con `confirm: true` exige `expected_version` y lo envía como `If-Match`.


## Cambios que no se versionan

Algunas escrituras quedan en la auditoría pero no crean versión de la zona:

- **Se simulan, no se versionan:** las purgas de caché y los certificados de origen (emitir y revocar). Llevan motivo y admiten simulación, pero no versión esperada.
- **No se simulan:** los clientes, la marca y la marca blanca. El `--dry-run` de la CLI solo enseña lo que enviaría y no envía nada.
- **Sin motivo:** comprobar ahora la delegación de una zona (`zones check`) y escanear la preparación para agentes no cambian la configuración.
