{
  "schemaVersion": 1,
  "slug": "webhook-reconciliation-deduplication",
  "locale": "zh",
  "evidenceClass": "executed-local-fixture-not-provider-generation",
  "providerModelTest": false,
  "productionTaskRun": false,
  "runCommand": "node run-recipe.mjs --out ./my-run",
  "environment": {
    "nodeVersion": "v24.18.0",
    "ffmpeg": null
  },
  "title": "用本地台账对账重复、乱序与漏发回调",
  "description": "实际运行 Node.js 离线回调重放，检查事件去重、终态保护，并用一次轮询补齐漏回调任务。",
  "goal": "保持同一任务身份，处理重复送达、迟到状态与漏回调。本配方只是离线集成练习；本站目前没有承诺向客户提供 Webhook。",
  "preparation": [
    "下载完整包并准备 Node.js。本次实际使用 Node v24.18.0，只调用内置 HTTP、文件与加密模块。",
    "查看 fixture.json：local-A 的成功事件重复送达后，又收到较早的 processing；local-B 完全缺少回调，通过一次本机状态读取恢复。",
    "选择不存在的输出目录。服务只绑定 127.0.0.1 的临时端口，只接受两个夹具任务 ID，并在 finally 关闭；不需要提供商密钥。"
  ],
  "steps": [
    {
      "title": "01. 先把回调绑定到已知任务",
      "body": "在解压目录运行命令。接收器先检查任务 ID、事件 ID，以及本例仅有的 processing/succeeded 状态词汇。未知任务与编造状态都实际返回 HTTP 400。这些是本地示例规则，不是对任何提供商请求体格式的声明。"
    },
    {
      "title": "02. 同一事件只应用一次",
      "body": "运行后查看 ledger.json。evt-1 首次把 local-A 改为 succeeded，再次收到相同事件 ID 时只记为 duplicate。本例去重集合保存在内存；真实接收器需要结合自己的数据库实现持久唯一约束与原子状态更新。"
    },
    {
      "title": "03. 阻止迟到状态覆盖完成态",
      "body": "不同事件 evt-2 随后带来 processing。因其等级较低，台账记为 stale，local-A 继续保持 succeeded。这个双状态模型只演示终态保护，没有规定真实 API 的取消、失败、重试或冲突终态如何处理。"
    },
    {
      "title": "04. 通过对账补齐缺失回调",
      "body": "local-B 在 GET /status/local-B 之前保持 processing；读取到匹配的任务 ID 与 succeeded 后更新原条目。验收一次轮询、零次 /generate，以及本机服务已关闭。失败回执还包含把轮询结果错误绑定到另一个任务的拒绝检查。"
    }
  ],
  "acceptance": [
    "十项命名检查全部通过，两个执行脚本的运行前后 SHA-256 一致。",
    "首次成功事件应用一次，相同事件 ID 重放被标记为 duplicate。",
    "迟到 processing 被标记为 stale，local-A 仍为 succeeded。",
    "未知任务与编造状态均返回 HTTP 400。",
    "local-B 对账前待完成，一次绑定任务的本机轮询后变为 succeeded。",
    "ledger.json 中生成请求为零，并验收临时回调服务确已关闭。"
  ],
  "troubleshooting": [
    {
      "symptom": "重复送达又更新了一次任务。",
      "change": "只调整事件 ID 的唯一性检查，再重放完全相同的 evt-1；第二次应明确记录 duplicate。"
    },
    {
      "symptom": "已完成任务退回处理中。",
      "change": "只修改状态转换守卫，再在成功后重放 evt-2；状态必须保持 succeeded。"
    },
    {
      "symptom": "任务始终收不到回调。",
      "change": "对原来的待完成身份执行一次绑定任务的状态读取，先核对返回 taskId 再更新，不重新提交生成。"
    }
  ],
  "ending": "保留事件台账与回执，用于证明这个本地状态归并器的行为。接入真实服务时仍需依据其回调支持、验签与持久化约定；本包没有承诺本站客户 Webhook。",
  "assetCaption": "原创事件泳道图、真实本地重放 JSON 与十项检查回执，未创建提供商任务或模型输出。"
}
