# 用本地台账对账重复、乱序与漏发回调

保持同一任务身份，处理重复送达、迟到状态与漏回调。本配方只是离线集成练习；本站目前没有承诺向客户提供 Webhook。

完整包只使用 Node 标准库。不需要安装媒体工具。

在本目录运行，并选择不存在的新输出目录，脚本拒绝覆盖。受内存管理的工作站可在 Node 命令前加本地串行执行包装器。不需要提供商密钥，夹具不会向设备外发请求。

```sh
node run-recipe.mjs --out ./my-run
```

实际记录：10项检查通过，包含1项预期拒绝，Node v24.18.0。未启动 FFmpeg 子进程。

准备

- 下载完整包并准备 Node.js。本次实际使用 Node v24.18.0，只调用内置 HTTP、文件与加密模块。
- 查看 fixture.json：local-A 的成功事件重复送达后，又收到较早的 processing；local-B 完全缺少回调，通过一次本机状态读取恢复。
- 选择不存在的输出目录。服务只绑定 127.0.0.1 的临时端口，只接受两个夹具任务 ID，并在 finally 关闭；不需要提供商密钥。

## 01. 先把回调绑定到已知任务

在解压目录运行命令。接收器先检查任务 ID、事件 ID，以及本例仅有的 processing/succeeded 状态词汇。未知任务与编造状态都实际返回 HTTP 400。这些是本地示例规则，不是对任何提供商请求体格式的声明。

## 02. 同一事件只应用一次

运行后查看 ledger.json。evt-1 首次把 local-A 改为 succeeded，再次收到相同事件 ID 时只记为 duplicate。本例去重集合保存在内存；真实接收器需要结合自己的数据库实现持久唯一约束与原子状态更新。

## 03. 阻止迟到状态覆盖完成态

不同事件 evt-2 随后带来 processing。因其等级较低，台账记为 stale，local-A 继续保持 succeeded。这个双状态模型只演示终态保护，没有规定真实 API 的取消、失败、重试或冲突终态如何处理。

## 04. 通过对账补齐缺失回调

local-B 在 GET /status/local-B 之前保持 processing；读取到匹配的任务 ID 与 succeeded 后更新原条目。验收一次轮询、零次 /generate，以及本机服务已关闭。失败回执还包含把轮询结果错误绑定到另一个任务的拒绝检查。

验收

- 十项命名检查全部通过，两个执行脚本的运行前后 SHA-256 一致。
- 首次成功事件应用一次，相同事件 ID 重放被标记为 duplicate。
- 迟到 processing 被标记为 stale，local-A 仍为 succeeded。
- 未知任务与编造状态均返回 HTTP 400。
- local-B 对账前待完成，一次绑定任务的本机轮询后变为 succeeded。
- ledger.json 中生成请求为零，并验收临时回调服务确已关闭。

排错

重复送达又更新了一次任务。只调整事件 ID 的唯一性检查，再重放完全相同的 evt-1；第二次应明确记录 duplicate。

已完成任务退回处理中。只修改状态转换守卫，再在成功后重放 evt-2；状态必须保持 succeeded。

任务始终收不到回调。对原来的待完成身份执行一次绑定任务的状态读取，先核对返回 taskId 再更新，不重新提交生成。

公开回执副本仅归一化本地 checkout 路径，publicPathNormalization 保存原始回执 SHA-256；未改动原件保留在非公开资产之外。真实回执位于 executed/。failure-receipt.json 记录预期负向案例，不表示套件断言失败。原创新夹具会重新生成到指定新目录，包内保留实跑输入输出便于复核。临时原始数据和本机 HTTP 服务在 finally 清理。

checksums.json 覆盖自身与 ZIP 以外的全部分发文件，避免循环自引用；ZIP 内包含同份清单。下载后可重算，但比较前提是预期清单可信。execution-receipt.json 记录实际脚本运行前后哈希，ZIP 也会由发布目录的资产清单绑定。

保留事件台账与回执，用于证明这个本地状态归并器的行为。接入真实服务时仍需依据其回调支持、验签与持久化约定；本包没有承诺本站客户 Webhook。

2026-10-14 实际读取的来源

- Node.js HTTP API: server lifecycle and requests：https://nodejs.org/api/http.html
- Node.js crypto API: SHA-256 hashing：https://nodejs.org/api/crypto.html
