# FSD M9 · 剧本库与投稿

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 M9](/qmtv/product/prd-qmtv-v1/)
> 实现落点：`backend/internal/domain/scriptlib/`（model/repo/service〔scriptlib.js 9+ensureSeed〕/service_studio〔studio.js 16〕/seeds）+ `cmd/server/scriptlib_handlers.go` + `cmd/server/routes.go:167-202`（挂载）。事实基准=Go 代码；冻结版 `qmtv/server/scriptlib.js`/`studio.js` 仅差异对拍参照。
> 域内要点：admin 9 端点叠 `AdminMiddleware`（403 forbidden）；**wire 全蛇形**（model.go json tag 亲证零驼峰）；两套正则刻意勿合并（seedEpisodeRe 带 `\s*` service.go:78 vs catalogRe 无 `:196`）；两套Genres 清单（scriptlib.js 11 项 service.go:25 vs studio.js studioGenres 11 项 service_studio.go:21——冻结死导出未消费，Go 不移植）；ID 前缀 sl_/su_/sub_/wp_/up_/na_（ids.go:29-50）。

### M9 剧本库与投稿

25 端点全量（scriptlib.js 9：用户 4+admin 5；studio.js 16：market 2/cover·rating 2/submissions 4/writer-profile 2/writers 2/admin·submissions 2/narrations 2）。公开写面唯一扣费点=解说成片 30 积分；审核 publish 多表写禁事务（置位→自动建档→入库→通知逐条）。PRD §4 M9 已登记差异 ①~⑨（prd-qmtv-v1.md:196）引用不重立。

#### F9.01 剧本库目录与检索（genres / list / :id / use）

> 对应 PRD：§4 M9 · GET /api/script-library/genres、GET /api/script-library、GET /api/script-library/:id、POST /api/script-library/:id/use ｜ 状态：实现 ｜ 实现落点：scriptlib_handlers.go:31-80、service.go:157-280、repo.go:285-391 ｜ UI：ScriptLibraryView + DashboardView 剧本库弹窗 ｜ 场景挂钩：无专门 e2e（E2E-SUB-01 间接）

##### 1 功能定义

平台剧本库只读消费面：五维目录（genres/markets/audiences/forms/stats）、四维筛选列表（含 7 日热度排序）、详情（试读+故事目录+使用计数）、「使用」埋点。

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

- 四端点均登录态（amw）；genres/list 首触惰性种子 `ensureSeed`（service.go:92-142：空表整插 3 条种子剧；否则按标题找 enabled=1 行仅回填占位值——market「中国内地」/""、audience「大众」/""、form「短剧」/""，service.go:70-74；content 无「第X集」标题整替为新分集版，seedEpisodeRe service.go:78）。
- 详情/使用前置：script 存在且 `enabled=1`（repo.go:347-357），否则 404「剧本不存在或已下架」。

##### 3 输入与校验

- list query 五参：genre/market/audience/form/search/sort——四维「全部」或空=忽略该维（repo.go:307-323）；search **TrimSpace** 后 LIKE title/description/tags 三字段（repo.go:324-328）；sort 三态 `rating`/`new`/缺省 hot（repo.go:331-337）。
- detail/use 路径参数 :id 无格式校验（查无→404 哨兵）。

##### 4 处理流程

- genres：ensureSeed → GROUP BY genre 产 stats（scripts/uses_7d/uses_total，repo.go:285-296，ORDER BY uses_7d DESC）→ 五键 payload 固定序 genres/markets/audiences/forms/stats（model.go:124-130；常量 11/3/4/3 项 service.go:25-28）。
- detail：preview=`preview_text||content 前 800 字`（service.go:248-251）→ char_count=**rune 数**（service.go:262，PRD ⑥）→ catalog 目录切分（`第X集` lookahead 手动切——RE2 不支持，service.go:202-238；filter(Boolean) 后 >1 段才产出〔前言段也算一段照抄〕；title=首行 ≤40 字，空回退「第 N 集」）→ uses_total/uses_7d 两计数。
- use：存在校验 → INSERT script_uses（su_ 前缀，service.go:276）→ 响应恒 `true`（**不回显行**）。

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

- 列表行=`s.*`+char_count/uses_total/uses_7d 三别名尾插（model.go:90-95，冻结 scriptlib.js:152 STATS_SELECT 键序）；详情=行+char_count/preview/catalog/uses_total/uses_7d（catalog 在 uses 前，model.go:105-112）。
- genres stats 行 {genre, scripts, uses_7d, uses_total}（model.go:116-121）。

##### 6 状态流转

script_library.enabled 只被 admin 面（F9.05 PUT enabled）翻转；本组四端点零写状态（use 仅追加 script_uses 流水）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 详情/使用 id 不存在或已下架 | 404 | 「剧本不存在或已下架」（model.go:201） |
| uses_7d 时间窗 | `created_at >= NOW() - INTERVAL 7 DAY`（repo.go:377，MySQL 方言） | — |
| 空表 stats | GROUP BY 零行→空数组（service.go:168-170 nil→[]，DIF-29-1 同族形态） | — |
| content 无分集标题 | catalog 空数组（≤1 段，service.go:226-228） | — |

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

四端点 `authed + amw`；script_uses 不去重（同一用户重复 use 多计——冻结同形照抄）；列表默认排 hot=`uses_7d DESC, uses_total DESC, rating DESC`（repo.go:331）。

#### F9.02 剧本市集（market/scripts 二件）

> 对应 PRD：§4 M9 · GET /api/market/scripts、GET /api/market/scripts/:id ｜ 状态：实现 ｜ 实现落点：scriptlib_handlers.go:214-237、service_studio.go:31-46、repo.go:421-468 ｜ UI：ScriptLibraryView 市集 tab ｜ 场景挂钩：无

##### 1 功能定义

已上架剧本的市集浏览面：与 F9.01 同构的列表/详情，但独立筛选维（无 audience）与**不同 404 文案**。

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

登录态；列表无 ensureSeed（与 scriptlib 面刻意不同——种子只保证库面有货）；详情前置 enabled=1。

##### 3 输入与校验

query 四参 genre/market/form/search/sort——「全部」忽略同 F9.01；**search 直用原值不 TrimSpace**（repo.go:439-440 注释「两处刻意不同源勿统一」——与 F9.04 writers trim 对比，PRD ⑧）。

##### 4 处理流程

列表 SELECT 与 F9.01 同 statsSelect 投影但独立 WHERE（market 面固定 enabled=1）；详情 `GetMarketScriptEnabled`（repo.go:457-468）。

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

列表行同 ScriptListRow；详情=ScriptLibrary 裸行（**无** preview/catalog/uses 派生键——与 F9.01 详情不同构，model.go:11-34）。

##### 6 状态流转

零写面（纯读）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 详情不存在/下架 | 404 | 「剧本不存在」（model.go:203——与 F9.01「剧本不存在或已下架」**不同字勿统一**） |

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

`authed + amw`；market 面不展示 disabled 行；sort 三态同 F9.01。

#### F9.03 投稿工作流（submissions 四件）

> 对应 PRD：§4 M9 · GET/POST /api/studio/submissions、PUT /api/studio/submissions/:id、POST /api/studio/submissions/:id/submit ｜ 状态：实现 ｜ 实现落点：scriptlib_handlers.go:271-329、service_studio.go:102-223、repo.go:532-593 ｜ UI：StudioView 投稿区 ｜ 场景挂钩：E2E-SUB-01（投稿链，**待实现**——人工步骤即未来断言主干）

##### 1 功能定义

编剧投稿全生命周期自助面：建稿（存草稿或直提）、编辑、提交送审、我的稿件列表。

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

登录态（amw）；编辑/提交前置：行存在**且属主**（`WHERE id=? AND user_id=?`，repo.go:557-568），否则 404「not found」。

##### 3 输入与校验

- create：title/content 必填 400「标题与内容必填」（service_studio.go:131-133）；截断 title 80/genre 12/market 20/form 12/audience 60/description 300（service_studio.go:137-142）；genre/market/form 缺省「都市」/「中国内地」/「短剧」。
- edit：全字段指针可选（nil=不动，COALESCE 形态 service_studio.go:182-203）；`Submit bool` 直传。

##### 4 处理流程

- create：status=`submit?'pending':'draft'`（service_studio.go:145-149）→ INSERT sub_ 行 → 响应 `{id, status}`。
- edit：状态守卫（仅 draft/needs_change/rejected 可编辑，:175-177）→ status=`submit?'pending':原值` → COALESCE 更新 → 恒 true。
- submit：**仅 draft** 可提交（:216-218）→ 置 pending（note 不动）→ 恒 true。

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

列表=本人窄投影 10 列**不含 content**（model.go:133-144，冻结 studio.js:171 列序）；create 响应 `{id, status}`（handlers :298）。

##### 6 状态流转

```mermaid
flowchart LR
  A["draft"] -->|"edit(submit)/submit"| B["pending"]
  B -->|"admin review"| C["needs_change"]
  B -->|"admin review"| D["rejected"]
  B -->|"admin publish"| E["published"]
  C -->|"edit/submit"| B
  D -->|"edit/submit"| B
```

needs_change/rejected 可再编辑回 pending；published/pending 不可编辑（400）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 编辑/提交非本人或不存在的 :id | 404 | 「not found」（model.go:205） |
| 编辑非 draft/needs_change/rejected | 400 | 「当前状态不可编辑」（model.go:218） |
| submit 非 draft | 400 | 「该稿件已提交过」（model.go:220） |
| 越权探测 | 属主过滤 404 不暴露存在性 | — |

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

四端点 `authed + amw` 无 admin；全部 user_id 属主过滤；content 全文仅 create/PUT 入库，列表不下发。

#### F9.04 编剧档案与编剧名录（writer-profile 二件 + writers 二件）

> 对应 PRD：§4 M9 · GET/POST /api/studio/writer-profile、GET /api/writers、GET /api/writers/:id ｜ 状态：实现 ｜ 实现落点：scriptlib_handlers.go:333-385、service_studio.go:227-320、repo.go:622-749 ｜ UI：StudioView 档案区 + WritersView ｜ 场景挂钩：无

##### 1 功能定义

编剧自助档案（一人一份 user_id UNIQUE）+ 公开编剧名录（列表/详情带 published 计数）。

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

profile GET 无前置（查无=null）；save 笔名必填；writers/:id 前置档案存在，否则 404「编剧不存在」。

##### 3 输入与校验

save：name 必填 400「笔名必填」（service_studio.go:258-260）；截断 name 30/bio 500/genres 120/works 500（:261-264）。

##### 4 处理流程

- GET：{profile, published} 双读（published=本人 script_library published 计数，repo.go:669-680）；profile 查无 **null 非 404**（service_studio.go:233）。
- save：exist→UPDATE 返回**原 id**；否则 INSERT wp_（:265-282）。
- writers 列表：6 列+published 子查询尾插（model.go:153-160）；**search TrimSpace 后 LIKE**（PRD ⑧ 用户裁决保持）。
- 详情：{...row, scripts, joined_at}（model.go:175-179）；scripts=该编剧 published LIMIT 30（repo.go:727-737）；joined_at=users.created_at 别名。

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

save 响应 `{id}`；writers 列表行/详情键序照冻结 studio.js:98-105（列表 SELECT）/:111-119（详情 `{...w, scripts, joined_at}`）。

##### 6 状态流转

writer_profiles 无状态列；published 计数随 admin review（F9.07）联动。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| 编剧详情 :id 不存在 | 404 | 「编剧不存在」（model.go:207） |
| joined_at 底层 users 行查无 | GetWriterJoinedAt 原生 QueryRow **透传错误→500**（repo.go:713；冻结 null 兜底——FK 语义死路径，**DIF-30-1** 登记） | — |

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

四端点 `authed + amw`（writers 名录非公开面）；档案一人一份（UNIQUE upsert 语义）；越权不可改他人档案（按 token user_id 定位）。

#### F9.05 管理面剧本库维护（admin script-library CRUD 四件）

> 对应 PRD：§4 M9 · GET/POST /api/admin/script-library、PUT/DELETE /api/admin/script-library/:id ｜ 状态：实现 ｜ 实现落点：scriptlib_handlers.go:84-210、service.go:284-492、repo.go:298/:393-420/:516-531 ｜ UI：AdminView 剧本库 tab ｜ 场景挂钩：无

##### 1 功能定义

平台剧本库管理面：全量列表（含 disabled）、建库、21 列可选更新、删除。

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

**adminMw**（403 forbidden）；list 首触 ensureSeed 同 F9.01；PUT/DELETE **不校验存在性**——id 不存在也恒 true（repo.go:397 注释「id 不存在也恒 ok」照抄冻结）。

##### 3 输入与校验

- create：title/content 必填 400「标题与剧本内容必填」（service.go:363-365）；截断 title 80/genre 12 缺省「其他」/description 300；ADMIN_FIELDS 11 扩展字段——TEXTUAL 6 项（highlights/preview_text/characters/worldview/synopsis/setting）截 **4000**、其余截 100（extraOf service.go:321-344，create 分叉 maxTextual=4000）；episodes/planned_chars 负数归 0（nonNeg service.go:516-521）；rating clamp 0..5（service.go:542-550）。
- put：同构全字段指针可选；COALESCE 21 列（repo.go:398-409）；不传字段不动。

##### 4 处理流程

create：19 列 INSERT（6 TEXT 列落**字符串空串非 NULL**——冻结显式绑定形态，service.go:144-155 注）→ 响应 `{id}`。delete：先删 uses 再删主行（两删序固定 service.go:484-492，禁事务铁律同形）。

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

list=ScriptListRow 同构但 `AdminAll` 无 enabled 过滤且固定 created_at DESC（repo.go:338-340）；put/delete 恒 `true`。

##### 6 状态流转

enabled 由 PUT 翻转（F9.01/F9.02 消费面即时可见）；无其他状态。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| create 缺 title/content | 400 | 「标题与剧本内容必填」（model.go:210） |
| PUT/DELETE id 不存在 | **200 恒 true**（0 行 UPDATE/先删 uses 0 行）——无 404 面（quirk 照抄冻结） | — |
| 非管理员访问 | 403 | 「forbidden」 |

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

四端点 `authed + adminMw`；删除级联仅两表（uses→主行；无 submissions 关联）；bind 失败静默按空载荷（`_ = c.Bind` handlers :119/:189——**DIF-30-2** 登记，DIF-29-23 同族）。

#### F9.06 批量上传与封面评分（upload + cover + rating）

> 对应 PRD：§4 M9 · POST /api/admin/script-library/upload、POST /api/admin/script-library/:id/cover、PUT /api/admin/script-library/:id/rating ｜ 状态：实现 ｜ 实现落点：scriptlib_handlers.go:132-182/:239-267、service.go:411-449、service_studio.go:48-98、repo.go:469-484 ｜ UI：AdminView 剧本库 tab（上传/封面/评分）｜ 场景挂钩：无

##### 1 功能定义

管理面批量导入 txt 剧本（GBK 自动回退）、单封面图上传、评分设定。

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

adminMw；upload 前置 multipart `files` 字段非空；cover 前置文件非空；**upload 修后语义**（PRD ①）：冻结 :248 run 16 参数对 9 占位符恒 RangeError——冻结版 upload 从未可用，Go 按 9 列 INSERT 修后实现（service.go:412-417 注）。

##### 3 输入与校验

- upload：单文件 ≤2MB（超→**500**「File too large」handlers :149-151 multer LIMIT_FILE_SIZE 语义）、≤20 文件（超→500「Unexpected field」:144-146）；路由级 BodyLimit 45mb 双保险（PRD ④）；文件名过 `gateway.FixFilename`（:162，GBK 坑守卫）；**extra 统一截 100 不走 TEXTUAL 4000**（service.go:426，与 create 分叉）。
- 内容判定：UTF-8 解码统计 U+FFFD 乱码率 > max(2, 长度×0.002) 回退 **GB18030** 解码（decodeUploadContent service.go:591-600，PRD ③）；TrimSpace 后 <30 字跳过（:430-432）。
- cover：缺文件 400「缺少封面图」（service_studio.go:52-54）；扩展名缺省 .png（extOf :70-75）。
- rating：clamp 0..5（service_studio.go:92-98）；nil rating 传 0（clampRating nil→0）。

##### 4 处理流程

- upload：逐文件解码→过滤→INSERT（title=`prefix+stripExt(文件名)` 截 80，genre 缺省「其他」截 12，service.go:432-443）→ `{created:[{id,title}], count}`；全跳过→400「没有有效剧本（文件过短或非文本）」（:445-447）。
- cover：storage.Put（up_ 前缀 ObjectKey 日期目录）→ cover_path 落库 `PathOf(key)`（**F8 口径绝对 URL**，PRD ②；冻结 /files/ 相对）→ 响应 `{cover: path}`。
- rating：UPDATE rating 单列 → 恒 true。

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

upload `{created, count}`；cover `{cover}`；rating `true`。

##### 6 状态流转

新库行 enabled=1 直接上架（upload 语义）；cover/rating 零状态。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| upload 无 files 字段 | 400 | 「未选择文件」（handlers :141-143） |
| 单文件 >2MB / >20 文件 | **500**（multer 语义对齐，PRD ④） | 「File too large」/「Unexpected field」 |
| 全部文件 <30 字或非文本 | 400 | 「没有有效剧本（文件过短或非文本）」（model.go:214） |
| cover 缺文件 | 400 | 「缺少封面图」（model.go:224） |
| storage 未配置（降级模式） | NoopStore.Put 报错 | **500**（ErrNotConfigured 在 errRespond 零映射落 default；503 仅 requireDB 前置可达——storage.New 恒装配 NoopStore） |

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

三端点 `authed + adminMw`；upload 每文件独立 su_ 无预生成（9 列 INSERT 不产 uses）；cover ObjectKey=up_ 前缀+原始扩展名。

#### F9.07 投稿审核与自动上架（admin/submissions 二件）

> 对应 PRD：§4 M9 · GET /api/admin/submissions、POST /api/admin/submissions/:id/review 🔒 ｜ 状态：实现 ｜ 实现落点：scriptlib_handlers.go:389-414、service_studio.go:325-417、repo.go:594-621/:738-759 ｜ UI：AdminView 投稿审核 tab ｜ 场景挂钩：E2E-SUB-01（publish 分支，待实现）

##### 1 功能定义

投稿审核台：按状态筛选全量列表 + 三动作审核（needs_change/reject/publish），publish 附带自动上架与建档。

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

adminMw；review 前置：投稿行存在（**不限属主**，repo.go:611-621），否则 404「not found」；action 三值白名单。

##### 3 输入与校验

GET query `status`（「全部」或空=全量，repo.go:594-610）；review body `{action, note}`——action ∉ {needs_change, reject, publish} → 400「未知操作」（service_studio.go:360-364）；note 截 200（:359）。

##### 4 处理流程（publish 四步禁事务）

1. 置状态+note（映射 needs_change→needs_change/reject→rejected/publish→published，:371；note 空回默认文案 `reviewNoteDefault`——publish 默认「已发布」，:344-349）。
2. publish 且无档案→自动建档 wp_（nickname 空回退「编剧」+user_id 前 4，:385-387）。
3. publish 入库 script_library（sl_ 前缀、preview_text=content 前 400，:392-405）。
4. 通知「投稿状态更新」：《title》：reviewNotice 文案（publish=**「已发布到剧本库」**——与 UPDATE note 默认「已发布」**不同字勿统一**，:336-342）；括号注记用**原始 note** 判空非默认值（:365-366 注）。

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

list=sub.*+u.email 尾插（model.go:147-150）；review 恒 `true`。

##### 6 状态流转

与 F9.03 §6 状态机同一张表（admin 侧三出边）；publish 终态+库行落地上架。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| :id 不存在 | 404 | 「not found」 |
| action 非三值 | 400 | 「未知操作」（model.go:222） |
| publish 四步中途失败 | 无事务回滚——半态留存（禁事务铁律=冻结无事务同形） | — |

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

`authed + adminMw`；review 不校验投稿当前状态（pending/rejected 均可再 publish——无状态机出边限制，quirk 照抄冻结）；通知零 SSE（M5 读面 60s 轮询消费）。

#### F9.08 解说一键成片（tools/narrations 二件）

> 对应 PRD：§4 M9 · POST /api/tools/narrations、GET /api/tools/narrations ｜ 状态：实现 ｜ 实现落点：scriptlib_handlers.go:418-443、service_studio.go:421-565、repo.go:760-784 ｜ UI：IndependentToolsView 解说工具 ｜ 场景挂钩：无

##### 1 功能定义

剧本解说词一键成片：同步 Mock 出 SVG 占位视频+解说分段 JSON，单次扣 30 积分。

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

登录态；script_text TrimSpace 后 <50 字 → 400「剧本文本过短（至少 50 字）」（service_studio.go:450-452）；余额 <30 → 402。

##### 3 输入与校验

`{title?, script_text}`；title 空→「解说成片」落库截 60（:457-458/:478）；script_text 落库截 100000 rune（:469）。

##### 4 处理流程

扣费先行（TryDeduct 30 `type='video'`「解说剧一键成片（-30）」taskID=NULL——0003 后 `''→NULL` 绑定收口，:453）→ 空行切段（`/\n\s*\n/` 等价贪婪切，splitOnBlankLines :527-555）≤30 段 → 每段取首个含「：」或「:」的行 ≤80 字（无则整段，firstColonLine :558-565）→ visual 按 i 奇偶交替（偶「主体中景推进」/奇「城市夜景空镜」:512-515）→ MakeVideo SVG 占位落盘（mgv_ 族 key）→ result JSON 固定键序 segments/cost/engine（:425-429；engine=管理台 video 引擎名）→ INSERT na_ 行 status=**'done'**（同步完成无轮询）。

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

create 响应 `{id, url, segments, cost}`（url=落盘 PathOf，降级模式空串）；list=本人 6 列窄投影（id/title/status/file_path/result/created_at，model.go:182-189）；result 原样 JSON 字符串下发。

##### 6 状态流转

status 恒 'done'（同步 Mock 无 processing/failed——**Mock 行为是契约的一部分**，总则 §2.4）。

##### 7 边界与异常

| 条件 | 系统行为 | 提示/码 |
|---|---|---|
| <50 字 | 400（扣费前校验） | 「剧本文本过短（至少 50 字）」（model.go:228） |
| 余额不足 | 402 | credits.ErrInsufficient 统一信封 |
| storage 未配置 | NoopStore.Put 报错→**500，扣 30 不退、任务不落**（service_studio.go:462-468 nil 守卫恒不触发——storage.New 恒装配 NoopStore，非「跳过照常」；参照 m13 同位置死路径表述） | — |

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

`authed + amw`；扣费台账 type='video' 与视频生成同族（credit-records 聚合归视频面）；PRD ⑨ task_id 已随 0003 NULL 化收口。

---

## 册尾端点覆盖对账（25/25）

对账 `backend/cmd/server/routes.go:167-202` 亲数 25 条 ↔ 本册 F9.01~F9.08：

| # | 端点（routes.go:line） | 节 | # | 端点（routes.go:line） | 节 |
|---|---|---|---|---|---|
| 1 | GET /script-library/genres（:176） | F9.01 | 14 | GET /studio/submissions（:191） | F9.03 |
| 2 | GET /script-library（:177） | F9.01 | 15 | POST /studio/submissions（:192） | F9.03 |
| 3 | GET /script-library/:id（:178） | F9.01 | 16 | PUT /studio/submissions/:id（:193） | F9.03 |
| 4 | POST /script-library/:id/use（:179） | F9.01 | 17 | POST /studio/submissions/:id/submit（:194） | F9.03 |
| 5 | GET /admin/script-library（:180）🔒 | F9.05 | 18 | GET /studio/writer-profile（:195） | F9.04 |
| 6 | POST /admin/script-library（:181）🔒 | F9.05 | 19 | POST /studio/writer-profile（:196） | F9.04 |
| 7 | POST /admin/script-library/upload（:182）🔒 | F9.06 | 20 | GET /writers（:197） | F9.04 |
| 8 | PUT /admin/script-library/:id（:183）🔒 | F9.05 | 21 | GET /writers/:id（:198） | F9.04 |
| 9 | DELETE /admin/script-library/:id（:184）🔒 | F9.05 | 22 | GET /admin/submissions（:199）🔒 | F9.07 |
| 10 | GET /market/scripts（:187） | F9.02 | 23 | POST /admin/submissions/:id/review（:200）🔒 | F9.07 |
| 11 | GET /market/scripts/:id（:188） | F9.02 | 24 | POST /tools/narrations（:201） | F9.08 |
| 12 | POST /admin/script-library/:id/cover（:189）🔒 | F9.06 | 25 | GET /tools/narrations（:202） | F9.08 |
| 13 | PUT /admin/script-library/:id/rating（:190）🔒 | F9.06 | | | |

注：🔒=adminMw；admin 9 端点（#5-9/12-13/22-23）。PRD §4 M9 差异 ①~⑨ 全引用不重立；本册新立 **DIF-30-1**（joined_at 500 vs 冻结 null 死路径）/ **DIF-30-2**（bind 静默按空载荷，DIF-29-23 同族）入 99-附录 B。

---

反链：

- [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/)
