# Supervisión, recuperación y replay

Supervisa fallos cooperativos por app y reproduce RPC, reloj y aleatoriedad con diarios acotados.

Estas APIs pertenecen al host y funcionan con el singleton o con una instancia privada `new WuCore()`. Importa sus funciones explícitamente. El core libera supervisores y sesiones al ejecutar `destroy()`.

<span class="section-anchor" id="section-1"></span>

## superviseApp

`superviseApp(core, appName, options?): WuHealthHandle`

La app debe estar registrada y solo puede tener un supervisor. Reporta fallos mediante `handle.report(error)` desde el host o `context.wu.reportError(error)` desde la app. Al alcanzar el umbral, el supervisor revoca el scope, desmonta la app y muestra un fallback; intenta recuperarla en el mismo contenedor y restaurar sus props.

```js
import { superviseApp } from 'wu-framework/health';
// runtime already registered checkout; #checkout-slot exists.
const health = superviseApp(runtime, 'checkout', {
  threshold: 3, windowMs: 30000, retries: 2, delay: 1000, maxDelay: 30000,
  onState: status => console.log(status.state),
  fallback: element => { element.textContent = 'Checkout unavailable'; },
});
await runtime.mount('checkout', '#checkout-slot');
// Teardown when the owner of this supervisor exits:
health.stop();
```

Los valores del ejemplo son los defaults. `threshold` y `retries` son enteros; `windowMs` mide la ventana de fallos. La recuperación usa espera exponencial limitada por `maxDelay` y consume como máximo `retries` intentos durante la vida del supervisor. `onState(status)` observa transiciones; `fallback(element, status)` personaliza el mensaje.

`status()` devuelve `{ appName, state, attempts, failures, error? }`. Los estados son `healthy`, `quarantined`, `recovering`, `exhausted` y `stopped`. `isBlocked()` es verdadero en cuarentena o agotamiento: el montaje manual rechaza con `WU_APP_QUARANTINED`. `settled()` espera la tarea actual, no toda la espera de futuros reintentos. `stop()` cancela recuperación y retira el fallback; no remonta la app.

La supervisión requiere errores reportados y código cooperativo. No interrumpe un bucle infinito ni detecta automáticamente todas las excepciones del navegador. Un segundo supervisor falla con `WU_HEALTH_ALREADY_SUPERVISED`; una app desconocida, con `WU_APP_NOT_REGISTERED`.

<span class="section-anchor" id="section-2"></span>

## createReplaySession

`createReplaySession(core, { apps, mode?, journal?, maxEntries?, maxBytes? }): WuReplaySession`

Registra llamadas scoped `request`, `now` y `random` para apps registradas. Usa `context.wu.now()` y `context.wu.random()` en el código reproducible. El modo predeterminado es `record`, con 5000 entradas y 2 MiB; no puede haber sesiones superpuestas para una misma app.

```js
import { createReplaySession } from 'wu-framework/replay';
import 'wu-framework/timeline';
// checkout performs its scoped operations during its awaited lifecycle.
const recording = createReplaySession(runtime, { apps: ['checkout'] });
await runtime.mount('checkout', '#checkout-slot');
const journal = await recording.export({ timeline: true });
recording.stop();
await runtime.unmount('checkout', { force: true });

const playback = createReplaySession(runtime, { apps: ['checkout'], mode: 'replay', journal });
try {
  await runtime.mount('checkout', '#checkout-slot');
  playback.assertComplete();
} finally {
  playback.stop();
}
```

Durante replay deben coincidir app, operación y entrada, incluido canal, datos y versión RPC. Devuelve el resultado o error grabado sin llamar al proveedor real. Una diferencia lanza `WU_REPLAY_MISMATCH`; datos no JSON producen `WU_REPLAY_VALUE` y superar límites produce `WU_REPLAY_LIMIT`. Reproduce entradas en el orden de invocación, no en el orden de finalización.

`status()` informa `{ mode, entries, cursor, pending, stopped, invalid }`. `export()` devuelve una promesa del formato `wu-replay/1` y rechaza si quedan RPC pendientes o el diario es inválido. `export({ timeline: true })` añade el timeline del mismo core; con la entrada core, importa timeline explícitamente. `assertComplete()` exige no tener pendientes ni entradas sin consumir en replay. `stop()` libera interceptores y permite volver a llamar a proveedores reales.

El diario no captura `fetch`, `Date.now`, `Math.random`, timers, DOM ni efectos externos directos. Tampoco restaura automáticamente el estado inicial ni ejecuta el timeline importado. Prepara las mismas entradas y estado antes de reproducir. Los diarios pueden contener datos de aplicación: controla su almacenamiento y acceso. Replay no es rollback ni auditoría durable.

<span class="section-anchor" id="section-3"></span>

## Evidencia y lifecycle

Implementación: `superviseApp` en `src/core/wu-health.js`, `createReplaySession` en `src/core/wu-replay.js` y facade en `src/core/wu-app-scope.js`. Pruebas: `tests/unit/app-health-supervision.test.js`, `tests/unit/replay-journal.test.js`, `tests/unit/replay-session.test.js` y `tests/unit/subsystem-lifecycle.test.js`.