{
  "blueprintId": "blueprint-25",
  "kind": "recipe",
  "evidenceClass": "fixture-run",
  "contentVersion": "pilot-7191236c6021d7811bbbd192",
  "title": "视频 API 任务断点恢复：有限轮询与结果下载校验",
  "description": "下载 Node.js 24 客户端，恢复已有公共 API 任务，处理 429 与终止状态，用检查点、尺寸限制和 SHA-256 保存结果。附 24 项已执行本机测试场景。",
  "goal": "客户端重启后继续查询同一个公共 API 任务，在明确期限内完成轮询并保存可校验的文件，避免再次提交生成任务。",
  "preparation": [
    "准备 Node.js 24，把 client.mjs 与 run-fixture.mjs 放到同一目录。",
    "找到已有公共 API 任务 ID 和该任务所属用户的密钥；网页生成任务可能不属于此接口的查询范围。",
    "仅在服务端进程环境变量中设置 SEEDANCE_API_KEY，选择自己控制的保存目录。",
    "若交付地址不在 minimaxh3.studio，先核对其准确托管 HTTPS 来源，再加入允许列表。"
  ],
  "steps": [
    {
      "title": "先跑本机接口契约测试",
      "body": "执行 node run-fixture.mjs。测试只访问 127.0.0.1，并使用带 MP4 前缀的合成字节。本次 Node v24.14.0 实跑通过 24 项场景；验证的是客户端流程，不是生成效果或视频播放。"
    },
    {
      "title": "恢复原来的任务 ID",
      "body": "运行 node client.mjs --task YOUR_EXISTING_API_TASK_ID --out ./saved-video。客户端只有 GET /api/v1/tasks/{id}，重启也不会提交新的生成请求。检查点不存密钥、原始响应和交付 URL。"
    },
    {
      "title": "按真实公开状态分支处理",
      "body": "直接读取任务对象，不使用 data 包裹。processing 继续等待；completed 必须有 output.video_url；failed 使用错误码退出。任务不存在或不属于同一公共 API 用户时，404 会停止流程。"
    },
    {
      "title": "给轮询和重试设明确上限",
      "body": "正常间隔 5 秒，429 与 503 按有上限的 Retry-After 或指数退避重试。所有请求共享 10 分钟期限与 120 次请求预算。超时后先检查并继续原任务，不自动创建替代任务。"
    },
    {
      "title": "只下载核对过的托管来源",
      "body": "必要时，在确认托管交付来源后加 --media-origin https://YOUR_VERIFIED_MEDIA_HOST。媒体请求不带 API 密钥；重定向、URL 用户密码、未知来源和非 MP4 类型会被拒绝。"
    },
    {
      "title": "验收文件并保留检查点",
      "body": "媒体先流式写入独占临时文件，默认上限 256 MiB。客户端检查 MP4 ftyp 前缀和 SHA-256，拒绝覆盖已有文件，并记录最终尺寸与哈希。再次运行已完成检查点时会先核对本地文件。真实视频还需另行播放和审核内容。"
    }
  ],
  "acceptance": [
    "本机测试通过回执中的 24 项场景，只发 localhost GET 请求，结束后关闭服务器。",
    "重启后继续使用原任务 ID，客户端没有 POST 或付费任务提交。",
    "检查点不含 API 密钥、原始任务响应和签名交付 URL。",
    "完成文件的字节数与 SHA-256 和检查点一致；被拒绝的下载会清理临时媒体。",
    "401/404、任务失败、期限/次数耗尽、未知来源、重定向、危险路径/软链接、超大输出或哈希不符都停止，不重新生成。"
  ],
  "troubleshooting": [
    {
      "symptom": "任务 ID 看起来正确，却返回 not_found。",
      "change": "确认任务来自公共 API，且属于同一密钥用户。不要默认网页任务也能在这里查询，更不要自动新建替代任务。"
    },
    {
      "symptom": "任务仍在 processing，轮询已经超时。",
      "change": "保留检查点，核对服务状态后用同一个任务 ID 再运行。只在明确且有限的运行预算内调整 --timeout-ms。"
    },
    {
      "symptom": "结果地址被拦截，或下载文件验证不通过。",
      "change": "先核对真实托管来源、预期尺寸和 MP4 格式，再改 --media-origin 或 --max-bytes。只有独立可信的哈希才能传给 --sha256。"
    },
    {
      "symptom": "任务已经 failed，想确认是否退款。",
      "change": "另行查看真实错误、credits_refunded 与 billing_status。本机测试不验证线上账单，客户端不重新生成也不执行退款。"
    }
  ],
  "ending": "源码契约与实跑测试证明了客户端恢复、停止和保存字节的方式。线上凭据、提供商完成、结算/退款、视频解码及模型效果仍未测试；接入生产前用已存在的明确任务分别验证。",
  "assetCaption": "原创 Node.js 客户端、24 项本机测试、双语运行说明与实际执行回执。合成测试字节不是生成视频。",
  "prompt": ""
}
