# FSD M4 · 导出交付

LLMS 索引： [llms.txt](/llms.txt)

---

> [← 总则](/qmtv/fsd-qmtv/00-%E6%80%BB%E5%88%99/) ｜ [附录 →](/qmtv/fsd-qmtv/99-%E9%99%84%E5%BD%95/) ｜ 上游：[PRD §4 M4](/qmtv/product/prd-qmtv-v1/)
> 实现落点：`backend/internal/domain/export/`（model/repo/service）+ `cmd/server/export_handlers.go` + `cmd/server/routes.go:82-92`（挂载）。事实基准=Go 代码；冻结版 `qmtv/server/index.js:434-471`/`routes2.js:318-439`/`routes3.js:347-376` 仅差异对拍参照。前端消费：ExportView.vue（编排）+ VideoView.vue（批量/下载面）。

### M4 导出交付

10 端点全量。本域是创作主链的出口：导出任务（同步伪完成）、批量打标/渲染（跨域调 generation）、三种 ZIP 交付（素材包/成片包/剪映草稿）。**蛇形 wire 域**（ExportView 消费 `shot_count` 等蛇形键——与任务域驼峰 `shotCount` 相反，二十六期 A1 的镜像面）；全域**零 SSE**（同步伪完成+前端 2s 轮询，service.go:4-5 注释）。计费两点：导出 2/段（type=export）、打标 10/镜写死（与 M3 tag 动态价**刻意不同源**）、渲染总额一次扣（VideoCost 公式）。zip 三端点二进制直出绕过 JSON 信封，错误分支仍走 JSON 信封。

#### F4.01 导出编排（export/task/:taskId / POST /export / export/:id）

> 对应 PRD：§4 M4 · GET /api/export/task/:taskId（①查重）、POST /api/export（②创建）、GET /api/export/:id（③轮询）｜ 状态：实现 ｜ 实现落点：export_handlers.go:17-56、export/service.go:57-113、repo.go:70-146 ｜ UI：`/export/:taskId`（ExportView GET-or-POST+2s 轮询编排骨架——E1 整页豁免页）｜ 场景挂钩：E2E-EXP-01（待实现，清单 :19）

##### 1 功能定义

导出任务三步编排：① 按任务查已有导出（防重复扣费）；② 无则创建——按有视频分镜数扣 2/段、**同步伪完成**（status=complete/progress=100/无产物落盘）；③ 轮询读单条。路由注记：`task/:taskId` 静态段与 `:id` 参数段并存，echo radix 静态子节点恒先于参数匹配（routes.go:83）。

##### 2 触发条件与前置状态

- 导出页挂载 → ①；404 则 ②；此后每 2s → ③（前端 ExportView.vue:69-103：`api.get('/export/task/${taskId}')` catch→`api.post('/export',{task_id:Number(taskId)||taskId})`→`setInterval(poll,2000)`，终态 `done|complete|failed` 清定时器）。

##### 3 输入与校验

| 端点 | 输入 | 校验 |
|---|---|---|
| GET /export/task/:taskId | 路径 taskId | 任务非本人/不存在 → 404 `not found`；无导出记录 → 同 404 |
| POST /export | `{"task_id":string}`（蛇形逐字） | 任务属主查无 → 404 **`task not found`（本端点专属文案，与全域 `not found` 不同字勿统一**，model.go:113 注释冻结 index.js:456）；余额不足 → 402 |
| GET /export/:id | 路径 id | 查无 → 404 `not found` |

② 的 `_ = c.Bind(&req)` 忽略解析错误——malformed JSON 时 req 零值续走业务（落 404 `task not found`）；冻结 express.json 对 malformed 抛 400 SyntaxError（中央中间件）——DIF-29-23。

##### 4 处理流程

```mermaid
flowchart TD
  A["①GET /export/task/:taskId"] -->|404| B["②POST /export"]
  A -->|命中| E["③轮询"]
  B --> C["TaskOwner 属主"]
  C -->|无| D["404 task not found"]
  C --> F["CountShotsWithVideo：video_id != ''"]
  F --> G["TryDeduct n×2 type=export<br/>「导出成片 N 段（-2n）」"]
  G -->|不足| H["402 积分不足"]
  G --> I["INSERT export_tasks<br/>status=complete progress=100 shot_count=n file_path=NULL"]
  I --> E
```

- ① 查重响应：`ExportTaskView{ExportTask,…,title}`（title 兜底空串——冻结 `.get(...)` undefined→'' 同）。
- ③ 轮询响应的 TaskTitle 查询**无属主过滤照抄**（repo.go:82-92 注记冻结 index.js:469 同——能命中的 export 行本就 user_id=?，无实际越权面）。
- **file_path null 往返**：DDL NOT NULL DEFAULT ''（P0 改造）→ Insert 落空串 → 读回 normalizeExportTask 空串→nil 还原响应 null 契约（repo.go:101-119——冻结 INSERT null 透传逐字保真）。

##### 5 输出与结果状态

- ② → `{"id","status":"complete","shot_count":n}`（三键，冻结 index.js:463 同）。
- ①③ → ExportTaskView：`id/user_id/task_id/status/progress/shot_count/file_path/created_at/title`（file_path null 透传——API-增量.md:99）；前端消费键 `title/shot_count/id/status/progress` 全蛇形。

##### 6 状态流转

**无状态机**：`status` 唯一写入方恒 `complete`+`progress:100`（service.go:73-77）；`processing` 仅是 DDL 默认/前端容错分支，实际不可达；无 update 路径、零 SSE——「伪完成」= 前端轮询首次即终态。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 任务不存在/非本人（②） | 404 | `task not found`（专属） |
| 任务不存在/无记录（①③） | 404 | `not found` |
| 余额不足 | 402 | `积分不足`（零台账，无退款路径——CAS 失败即止） |
| 零分镜有视频 | 正常创建 shot_count=0 | — |
| malformed JSON（②） | 零值续走 → 404（DIF-29-23） | — |

##### 8 权限与数据规则

- 权限：登录态+tasks 双键属主（`id=? AND user_id=?`）。
- 数据规则：写 `export_tasks/credit_records`；ID 前缀 `e_`（ids.go:22——冻结 uid('e_')，P0 误写 ex_ 已 W5 勘误）；export_tasks 含 shot_count 列（**F3 补列修正**——冻结 db.js 无该列而 INSERT 显式引用=冻结版该端点实际抛错，Go 按修后语义建列，PRD :103/:164 差异④）。

#### F4.02 一键串行打标（tasks/:id/export/tag-all）

> 对应 PRD：§4 M4 ④ · POST /api/tasks/:id/export/tag-all ｜ 状态：实现 ｜ 实现落点：export_handlers.go:57-66、export/service.go:120-148、repo.go:160-188 ｜ UI：VideoView「一键打标」（confirm 文案 `将对全部 N 个分镜串行打标，消耗 N*10 积分`——前端重复实现价）｜ 场景挂钩：无

##### 1 功能定义

对任务全部分镜的视频提示词**串行追加**一致性标签补丁；按 10/镜写死扣费（type=tag）。

##### 2 触发条件与前置状态

- 导出准备区按钮（confirm 前置）→ `POST /api/tasks/:id/export/tag-all`（Bearer）。

##### 3 输入与校验

路径 `:id`，**无 body**。TaskOwner 查无 → 404 `not found`。

##### 4 处理流程

ListShotsAll（ORDER index_no）→ **先扣总额后逐镜 patch**（`cost := 10 * len(shots)` 写死，service.go:129 注释「与单镜 Tag 的 F6 动态价不同源」；desc `一键串行打标签 ×N（-10N）`，type=tag）→ 逐镜 `UPDATE shots SET video_prompt = CONCAT(COALESCE(video_prompt,''), ?)`，patch 逐字 `` `\n[一致性标签·自动] 主体一致 / 光影连续 / 运镜连贯 / <锚定上一镜|独立转场>` ``（MySQL 无 `||` 拼接→CONCAT 等价转换——API-增量.md:108 已登记）。

冻结 routes2.js:325 的 notifications INSERT 预备语句系**死代码未调用**，Go 不复刻（service.go:119 注释）。

##### 5 输出与结果状态

`{"tagged":len(shots),"cost":cost}`。

##### 6 状态流转

无状态机；shots.video_prompt 尾部追加（可重复点击重复追加——冻结同）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 任务非本人 | 404 | `not found` |
| 余额不足 | 402 | `积分不足` |
| 循环中途 UPDATE 失败 | 500（部分 patch 已落库无回滚——冻结同无事务） | `internal server error` |

##### 8 权限与数据规则

- 权限：登录态+任务属主；写 `shots/credit_records`。

#### F4.03 批量极速渲染（tasks/:id/export/render-all）

> 对应 PRD：§4 M4 ⑤ · POST /api/tasks/:id/export/render-all ｜ 状态：实现 ｜ 实现落点：export_handlers.go:69-78、export/service.go:151-202、generation/service_video.go:539-569（RenderOne）｜ UI：VideoView「批量极速渲染」（confirm 估算文案含拉取+每秒价）｜ 场景挂钩：无

##### 1 功能定义

对缺视频分镜批量提交渲染：**总额一次扣**（Σ VideoCost），逐镜串行调 generation.RenderOne；单镜失败退单价（退款唯一口在 generation 内），本层不重复退。

##### 2 触发条件与前置状态

- 渲染按钮（confirm 前置）→ `POST /api/tasks/:id/export/render-all`（Bearer）。

##### 3 输入与校验

路径 `:id`，无 body。TaskOwner 查无 → 404。**空表不扣费单键响应** `{"rendered":0}`（service.go:162-166 冻结 :338 同）。

##### 4 处理流程

```mermaid
flowchart TD
  A["POST render-all"] --> B["TaskOwner 404 面"]
  B --> C["ListShotsNoVideo：video_id = '' ORDER index_no"]
  C -->|空| D["200 {rendered:0} 不扣费"]
  C --> E["Σ VideoCost(secs=duration‖5) 一次 TryDeduct<br/>「批量极速渲染 ×N（-Σ，模型名）」type=video"]
  E -->|不足| F["402"]
  E --> G["逐镜串行 RenderOne"]
  G -->|err| H["failed++ continue（退款在 generation 内认领制）"]
  G -->|processing| I["processing++"]
  G --> J["note 两段：N 段失败已退款；N 段已提交供应商队列…"]
```

- RenderOne 细节（generation 侧）：secs 0→5；resolution 白名单外归 `1080p`（B5 收紧向已登记）；**prompt 裸传不加 `[resolution]` 后缀**（与 M3 ⑩ generate-v2 的关键差异，service_video.go:540-541 注释）；返回 status `'processing'/'done'`。
- 退款：RenderOne 真实通道提交失败 → SetVideoFailed+SetShotStatus(failed)+Refund（`ClaimVideoRefund` 认领防重，desc `批量渲染失败自动退回（+N）：<cause截80>`）→ 502 被本层吞为 failed++（与冻结 catch 同）。

##### 5 输出与结果状态

`{"rendered","failed","cost","processing","note"}` 五键恒在（全 Mock done 场景 processing=0、note 空串——service_test.go:396-397 场景锁）。

##### 6 状态流转

videos 行 `processing`（真实异步）或 `done`（Mock）；shots.status 随 Link 面联动（generation 域内）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 任务非本人 | 404 | `not found` |
| 无缺视频分镜 | 200 | `{"rendered":0}` |
| 余额不足 | 402 | `积分不足` |
| 单镜失败 | 退单价（generation 内）+ failed++ | — |

##### 8 权限与数据规则

- 权限：登录态+任务属主；跨域经 `BatchVideoGateway` 编译期断言接 generation（generation_assembly.go:118）。
- 数据规则：计价 `video_pull + video_per_second×secs`（secs<=0→5，默认 5/5 settings 可调）；note 模板冻结 routes2.js:351 逐字。

#### F4.04 素材包交付（tasks/:id/export/assets-deliver）

> 对应 PRD：§4 M4 ⑥ · GET /api/tasks/:id/export/assets-deliver ｜ 状态：实现 ｜ 实现落点：export_handlers.go:81-90、export/service.go:204-257、repo.go:255-265 ｜ UI：VideoView 下载素材包（前端自命名 qmtv-assets.zip，不读 Content-Disposition）｜ 场景挂钩：无（EXP-01 前置族）

##### 1 功能定义

任务全部图片（storyboard/asset/anchor 三 kind+shot 关联图）打包 ZIP，**无条件附 prompts.txt**（全分镜图片/视频提示词清单）。零扣费。

##### 2 触发条件与前置状态

- 下载按钮 → `GET /api/tasks/:id/export/assets-deliver`（Bearer）。

##### 3 输入与校验

路径 `:id`。TaskOwner 查无 → 404；图片行为空 → 400 `没有可下载的图片`。

##### 4 处理流程

ListTaskImages（`(i.task_id=? AND i.kind IN ('storyboard','asset','anchor')) OR (s.task_id=?)` LEFT JOIN shots ORDER index_no）→ 内存 zip 逐行：file_path 空/`store.Read` 失败**静默跳过**（冻结 existsSync=false 同）；seen 去重在 Read 成功后置位（冻结同序）；条目名 `images/` + 前缀（`IndexNo != nil && != 0` → `shot%03d_` 否则 `asset_`——**W6 期修对齐冻结 JS truthy 语义**，0 与 null 同落 asset_；PRD 差异②引用）+ `safeSlice(id,4,12)`（img_ 前缀后 8 字符）+ 扩展名。prompts.txt：每镜 `# 分镜%d 【%s】\n[图片提示词]\n%s\n\n[视频提示词]\n%s\n` 以 `\n` join。Deflate 压缩。

##### 5 输出与结果状态

二进制 ZIP 直出：`Content-Type: application/zip` + `Content-Disposition: attachment; filename="qmtv-assets-<taskID前8>.zip"`（前端不读该头自命名）。全部图片缺失仍 200（zip 只剩 prompts.txt）。

##### 6 状态流转

无（纯打包）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 任务非本人 | 404 | `not found` |
| 无图片行 | 400 | `没有可下载的图片` |
| 个别文件缺失 | 静默跳过该条目 | — |

##### 8 权限与数据规则

- 权限：登录态+任务属主；只读 images/shots。

#### F4.05 成片包交付（tasks/:id/export/deliver）

> 对应 PRD：§4 M4 ⑦ · GET /api/tasks/:id/export/deliver ｜ 状态：实现 ｜ 实现落点：export_handlers.go:93-102、export/service.go:259-293、repo.go:221-230 ｜ UI：VideoView 下载成片（qmtv-videos.zip）｜ 场景挂钩：E2E-EXP-01（ZIP 魔数断言目标面，待实现）

##### 1 功能定义

任务全部 `status='done'` 视频打包 ZIP，条目名含场景标题清洗。零扣费。

##### 2 触发条件与前置状态

- 下载按钮 → `GET /api/tasks/:id/export/deliver`（Bearer）。

##### 3 输入与校验

路径 `:id`。TaskOwner 查无 → 404；无 done 视频 → 400 `没有已完成的视频`。

##### 4 处理流程

ListDoneVideos（`v.task_id=? AND v.status='done'` LEFT JOIN shots ORDER index_no）→ zip 逐行：**file_path 空行跳过**（冻结 `path.basename('')` → readFileSync(FILES_DIR) 目录崩溃 500——Go 跳过不复刻崩溃，service.go:259-260 注释显式登记）；条目名 `shot%03d_<scene_title 清洗><ext>`：IndexNo 0 兜底同冻结 `v.index_no || 0`；cleanFilename：`[\\/:*?"<>|]` → `_` 且截 20（Go 按 rune、JS 按 UTF-16 单元——中文 BMP 等价）。

##### 5 输出与结果状态

ZIP 直出，`filename="qmtv-videos-<id8>.zip"`。全部文件缺失 → 空 zip 200（冻结同语义）。

##### 6 状态流转

无。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 任务非本人 | 404 | `not found` |
| 无 done 视频 | 400 | `没有已完成的视频` |
| file_path 空行 | 跳过（冻结崩溃 500 不复刻——DIF-29-24） | — |

##### 8 权限与数据规则

- 权限：登录态+任务属主；只读 videos/shots。

#### F4.06 单镜参数（tasks/:id/export/scenes/:sid）

> 对应 PRD：§4 M4 ⑧ · PUT /api/tasks/:id/export/scenes/:sid ｜ 状态：实现 ｜ 实现落点：export_handlers.go:105-120、export/service.go:295-327、repo.go:192-219 ｜ UI：VideoView 单镜参数表单（**双 PUT 串行**：本端点成功才发 `/shots2/:id/params` 分辨率）｜ 场景挂钩：无

##### 1 功能定义

单镜 duration/ratio 参数更新：duration clamp [1,15]、ratio 白名单外归 `16:9`。零扣费。

##### 2 触发条件与前置状态

- 单镜参数保存 → `PUT /api/tasks/:id/export/scenes/:sid`（Bearer）。

##### 3 输入与校验

| 字段 | 来源 | 必填 | 校验 |
|---|---|---|---|
| duration | JSON body | 否（*int） | **键缺失或 null 均跳过**（对齐冻结 `duration != null`）；clamp <1→1、>15→15 |
| ratio | JSON body | 否（*string） | 非 nil 且非空串才处理；白名单 `9:16/16:9/1:1`，**白名单外归 '16:9'**（冻结 routes2.js:403 同） |

三键属主存在性：`shots JOIN tasks WHERE s.id=? AND t.id=? AND t.user_id=?`，!ok → 404 `not found`。

##### 4 处理流程

逐键独立 UPDATE（duration 一条、ratio 一条）。

##### 5 输出与结果状态

`{"ok":true,"data":true}`。

##### 6 状态流转

shots.duration/ratio 列更新；无其他联动。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 镜/任务不匹配或非本人 | 404 | `not found` |
| duration=0/20 | clamp 1/15 | — |
| ratio=4:3 | 归 `16:9` 200 | — |

##### 8 权限与数据规则

- 权限：登录态+三键属主链（shot+task+user）。

#### F4.07 导出执行记录（tasks/:id/export/history）

> 对应 PRD：§4 M4 ⑨ · GET /api/tasks/:id/export/history ｜ 状态：实现 ｜ 实现落点：export_handlers.go:123-132、export/service.go:329-349、repo.go:232-253 ｜ UI：VideoView 导出记录区（useQuery `['export-history', taskId]`）｜ 场景挂钩：无

##### 1 功能定义

任务关联全部视频的执行记录读（**无 status 过滤**），每行注入模型名与参考图计数。零扣费。

##### 2 触发条件与前置状态

- 记录区加载 → `GET /api/tasks/:id/export/history`（Bearer）。

##### 3 输入与校验

路径 `:id`。TaskOwner 查无 → 404 `not found`。

##### 4 处理流程

ListHistoryVideos（`v.*, s.index_no, s.scene_title` LEFT JOIN shots ORDER v.created_at DESC——**无 status 过滤**、无次级排序键）→ 每行注入 `model = billing.View().VideoModel`（冻结 getBilling().models.video 同）→ 每行 `ref_images = COUNT(shot_images WHERE shot_id=? AND image_id IS NOT NULL)`（Go ShotID 空串计 0——冻结 `v.shot_id || ''` 同语义；**每行一次 N+1 查询，冻结同构保真**）。

##### 5 输出与结果状态

VideoHistoryRow 数组，行键 18 个：`id,user_id,task_id,shot_id,standalone,prompt,status,file_path,error,attempts,seconds,resolution,refunded,created_at,index_no,scene_title,model,ref_images`（version 不出 wire）。`index_no`/`scene_title` LEFT JOIN 可空→指针 null。

##### 6 状态流转

无（纯读）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 任务非本人 | 404 | `not found` |
| 无视频 | 200 空数组 | — |

##### 8 权限与数据规则

- 权限：登录态+任务属主。
- 数据规则：**已登记差异①**——响应 `v.*` 列序与冻结（db.js CREATE 后 ALTER 折叠序）不齐：键集一致、前端按键取值无感，严格逐字节对拍不齐（PRD :164；ExportTaskView 字段序同族）。

#### F4.08 剪映草稿（tasks/:id/jianying-draft）

> 对应 PRD：§4 M4 ⑩ · GET /api/tasks/:id/jianying-draft ｜ 状态：实现 ｜ 实现落点：export_handlers.go:135-144（zipRespond :148-151）、export/service.go:353-457 ｜ UI：VideoView 下载剪映草稿（qmtv-jianying.zip）｜ 场景挂钩：无

##### 1 功能定义

按 done 视频序列生成剪映草稿演示包：draft_content.json（μs 时间轴）+ 素材/ + 使用说明.txt 三件套 ZIP。零扣费。

##### 2 触发条件与前置状态

- 下载按钮 → `GET /api/tasks/:id/jianying-draft`（Bearer）。

##### 3 输入与校验

路径 `:id`。TaskOwner 查无 → 404；无 done 视频 → 400 `没有已完成的视频`。

##### 4 处理流程

ListDoneVideos（同 F4.05 SQL）→ folder=`QMTV剪映草稿_<title清洗截20>` → draft 键序逐字 `{name, duration, materials:{videos:[{id,path,duration}]}, tracks:[{type,segments:[{material_id,start,duration}]}]}`，初始 `tracks:[{type:"video"}]` → 逐视频：secs=seconds 0→5、`us=secs×1_000_000`、fname=`shot%03d.svg`（**IndexNo==0 → i+1 兜底；视频文件 .svg 后缀怪形态逐字照抄**，service.go:380-381 注释冻结 routes3.js:363 同）→ materials push `{id:"mat_<i>", path:"./素材/<fname>", duration:us}`、segments push `{material_id:"mat_<i>", start:i×us, duration:us}` → **draft 引用与 zip 收录解耦**（draft push 无条件；zip 仅 store.Read 成功且 file_path≠"" 收录——冻结 routes3.js:362-368 同）→ `draft_content.json` MarshalIndent 缩进 2 → `使用说明.txt` 五步说明+「正式版将生成完整剪映工程格式」提示逐字。

##### 5 输出与结果状态

ZIP 直出，`filename="qmtv-jianying-<id8>.zip"`：

```
qmtv-jianying-<taskID前8>.zip
└── QMTV剪映草稿_<title清洗截20>/
    ├── 素材/shot001.svg…        # 视频字节、.svg 后缀怪形态
    ├── draft_content.json       # 缩进 2；duration=Σμs
    └── 使用说明.txt
```

##### 6 状态流转

无（纯打包）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 任务非本人 | 404 | `not found` |
| 无 done 视频 | 400 | `没有已完成的视频` |
| 素材文件缺失 | draft 仍引用、zip 缺该条目（解耦保真） | — |

##### 8 权限与数据规则

- 权限：登录态+任务属主。
- 数据规则：draft `duration` 由循环累加（冻结 reduce 计算——等价）；`mat_<i>` 物料 id 从 0 起。

---

### M4 端点覆盖对账

| # | 端点 | 功能节 |
|---|---|---|
| 1 | POST /api/export | F4.01 |
| 2 | GET /api/export/task/:taskId | F4.01 |
| 3 | GET /api/export/:id | F4.01 |
| 4 | POST /api/tasks/:id/export/tag-all | F4.02 |
| 5 | POST /api/tasks/:id/export/render-all | F4.03 |
| 6 | GET /api/tasks/:id/export/assets-deliver | F4.04 |
| 7 | GET /api/tasks/:id/export/deliver | F4.05 |
| 8 | PUT /api/tasks/:id/export/scenes/:sid | F4.06 |
| 9 | GET /api/tasks/:id/export/history | F4.07 |
| 10 | GET /api/tasks/:id/jianying-draft | F4.08 |

10/10 对账 routes.go:82-92 与 PRD §4 M4。已登记差异引用：PRD 差异①（history 列序）②（index_no=0 truthy——W6 修后形态）③（repo 16 方法计数）④（shot_count 列 F3 补列修正）；API-增量.md:108（CONCAT 方言/video_id DDL 谓词/file_path null 往返）三条。

**注记（非 DIF）**：① routes.go:80 注记引用「PRD :136」行号漂移（M4 清单实际在 :162）——文档面，留勘误；② E2E-EXP-01 长期「待实现」且冒烟列=是（清单 :19），三十一期反向覆盖收口候选；③ ⑤ RenderOne 单镜重算 cost 与响应回传调用方总额之间存在 settings 中途变更窗口（单请求内，实践不可达）。

**DIF 新发现（本期取证，入 [99-附录 B](/qmtv/fsd-qmtv/99-%E9%99%84%E5%BD%95/)）**：DIF-29-23 export ②⑧ bind 错误静默忽略（零值续走业务；冻结 malformed JSON 400 SyntaxError 中央中间件）／DIF-29-24 deliver 空 file_path 行跳过 200（冻结 readFileSync 目录崩溃 500——service.go:259-260 代码注释已登记、PRD §4 M4 正文未含）。

---

反链：

- [QMTV 功能规格说明书（FSD）v1.0 · 总则](/qmtv/fsd-qmtv/00-%E6%80%BB%E5%88%99/)
- [FSD 附录 · PRD 对照 / 代码域索引 / 差异台账](/qmtv/fsd-qmtv/99-%E9%99%84%E5%BD%95/)
- [FSD M2 · 任务与拆解](/qmtv/fsd-qmtv/m02-%E4%BB%BB%E5%8A%A1%E4%B8%8E%E6%8B%86%E8%A7%A3/)
