# 监督、恢复与 replay

监督每个应用的合作式故障，并用有界 journals 重放 RPC、时钟和随机性。

这些 APIs 属于 host，可用于单例或私有 `new WuCore()` 实例。请显式导入其函数。执行 `destroy()` 时，core 会释放 supervisors 和 sessions。

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

## superviseApp

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

应用必须已注册，且只能有一个 supervisor。host 通过 `handle.report(error)` 报告故障，应用通过 `context.wu.reportError(error)` 报告。达到阈值后，supervisor 撤销 scope、卸载应用并显示 fallback；随后尝试在同一容器恢复并还原 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();
```

示例值即默认值。`threshold` 和 `retries` 为整数；`windowMs` 表示故障统计窗口。恢复使用受 `maxDelay` 限制的指数等待，在 supervisor 整个生命周期内最多消耗 `retries` 次尝试。`onState(status)` 观察转换；`fallback(element, status)` 自定义消息。

`status()` 返回 `{ appName, state, attempts, failures, error? }`。状态包括 `healthy`、`quarantined`、`recovering`、`exhausted` 和 `stopped`。隔离或耗尽时 `isBlocked()` 为真，手动挂载以 `WU_APP_QUARANTINED` 拒绝。`settled()` 等待当前任务，而非未来所有重试等待。`stop()` 取消恢复并移除 fallback，不重新挂载应用。

监督依赖已报告的错误和合作式代码。它无法中断无限循环，也不会自动检测所有浏览器异常。第二个 supervisor 会产生 `WU_HEALTH_ALREADY_SUPERVISED`；未知应用会产生 `WU_APP_NOT_REGISTERED`。

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

## createReplaySession

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

为已注册应用记录 scoped `request`、`now` 和 `random` 调用。在可重放代码中使用 `context.wu.now()` 和 `context.wu.random()`。默认模式为 `record`，限制为 5000 条记录和 2 MiB；同一应用不能有重叠 sessions。

```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();
}
```

重放时应用、操作和输入必须一致，包括 RPC 通道、数据和版本。它返回已记录结果或错误，不调用真实 provider。差异会抛出 `WU_REPLAY_MISMATCH`；非 JSON 数据产生 `WU_REPLAY_VALUE`，超限产生 `WU_REPLAY_LIMIT`。记录按调用顺序而非完成顺序重放。

`status()` 报告 `{ mode, entries, cursor, pending, stopped, invalid }`。`export()` 返回 `wu-replay/1` 格式的 promise；若仍有 RPC 待处理或 journal 无效则拒绝。`export({ timeline: true })` 添加同一 core 的 timeline；使用 core 入口时需显式导入 timeline。`assertComplete()` 要求没有待处理调用或未消费重放条目。`stop()` 释放 interceptors，允许重新调用真实 providers。

journal 不捕获直接 `fetch`、`Date.now`、`Math.random`、timers、DOM 或外部副作用，也不会自动恢复初始状态或执行导入的 timeline。重放前应准备相同输入和状态。Journals 可能包含应用数据，请控制存储和访问。Replay 不等于回滚或持久审计。

<span id="evidencia-y-lifecycle"></span>

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

## 证据与生命周期

实现：`src/core/wu-health.js` 中的 `superviseApp`、`src/core/wu-replay.js` 中的 `createReplaySession` 和 `src/core/wu-app-scope.js` 中的 facade。测试：`tests/unit/app-health-supervision.test.js`、`tests/unit/replay-journal.test.js`、`tests/unit/replay-session.test.js` 和 `tests/unit/subsystem-lifecycle.test.js`。