docs: 登记 QA-CR 全项目 code review 整改批(21 项任务表)

This commit is contained in:
Yaojia Wang
2026-06-27 06:35:55 +02:00
parent a6f5d085e5
commit 3bd464d400

View File

@@ -8,7 +8,42 @@
---
## 当前阶段:T6 · 创作工具箱(通用生成器框架)— P1 框架地基
## 当前阶段:QA-CR · 全项目 Code Review 整改批
> **背景**[2026-06-26] 对全仓做了一次**七维度 Code Review**(后端 Python 规范 / LangGraph 最佳实践 / 后端 FastAPI 架构 / 前端编程规范 / 前端架构 / 应用安全 / 依赖时效6 个专科 reviewer 并行 + 依赖自查。绿门禁下仍揪出 **2 CRITICAL + 13 HIGH + 一批 MEDIUM/LOW**——再次证伪「测试过=没问题」。架构本身成熟、不变量纪律强provider 中立 / HITL accept 单写 / checkpoint 仅控制流 / SecretStr 全覆盖);**确认无虞**SSRF`base_url` 仅来自硬编码 `_PROVIDER_BASE_URLS`,无用户 URL 入网)/ SQL 注入(全 ORM 参数化)/ XSS无 `dangerouslySetInnerHTML`/`eval`/ 错误信封不泄栈密钥 / Kimi 用 RFC 8628 device flow 无 CSRF 面。
> **整改 DoD**CRITICAL + HIGH 全部 ✅ 且各自补回归测试MEDIUM 择批清理;门禁全绿(后端 ruff/format/mypy/pytest + alembic 无漂移;前端 lint/tsc/vitest/build。**守 §Definition of Done**:每项 TDD 先红后绿、完成即小步 commit。
> **落地顺序**:① 本周 CR-C1/C2 + 两资源泄漏H1/H2+ Fernet keyH8② 合并前 计费两项H3+ request_idH4+ max_lengthH9+ 前端三 HIGHH10/H11/H12③ MEDIUM/LOW 技术债批。
> **依赖纪律**CR-C1升 Next须 `pnpm install` 重锁 + `pnpm build` 验证、**升级后轮换全部应用密钥**(与 CR-H8 Fernet key 联动。CR-H4/H9 改 schema → 若动 OpenAPI 形参须 `pnpm gen:api` + `memory/contracts.md` 记一笔。无 DB 迁移预期。
| 任务 | 状态 | 负责 | 依赖 | 备注 |
|---|---|---|---|---|
| CR-C1 🔴 升级 Next.js 修 RCECVE-2025-66478+ React 补丁 + 轮换密钥 | ⬜ | @frontend | — | `apps/web/package.json` next **15.1.3→15.1.9**(或 15.5.7/16.xreact/react-dom 19.0.0→补丁版(上游 CVE-2025-55182App Router + RSC 应用正受影响。`pnpm install` 重锁 + `pnpm build` 验;**升级后轮换全部应用密钥**(联动 CR-H8。Next15 LTS 2026-10-21 EOL规划 16.x 迁移 |
| CR-C2 🔴 多章链设 `recursion_limit` | ⬜ | @backend | — | `services/chain_runner.py:210` 默认 25 supersteps、每章 4 节点→超 ~6 章即 `GraphRecursionError` 整 job 失败,「批量产一卷(K章)」在目标规模不可用。`config``recursion_limit=(last-start+1)*4+10`;补超默认上限的 mock-gateway 回归测试 |
| CR-H1 🟠 Kimi OAuth 轮询不占 DB 会话 | ⬜ | @backend | — | `services/job_runner.py:75`+`routers/kimi_oauth.py:85` 轮询全程(最坏 >200s占连接池连接、饿死其他请求轮询循环无需 DB仅最终 `upsert_oauth_credential` 开短会话 |
| CR-H2 🟠 修 `httpx.AsyncClient` 泄漏 | ⬜ | @backend | — | `routers/kimi_oauth.py:73` `Depends(_default_http_client)` 非 generator 依赖、永不 `aclose()`;改 `async def _http_client(): async with httpx.AsyncClient(...) as http: yield http` |
| CR-H3 🟠 Anthropic 结构化输出记真实 usage | ⬜ | @llm | — | `adapters/anthropic.py:161` 硬编码 `usage=0``usage_ledger` 全记 0 成本、腐蚀计费(违 ARCH §4.8);用 `create_with_completion` 取原始响应 usage |
| CR-H4 🟠 `request_id` 贯通网关日志 | ⬜ | @llm | — | `gateway.py:183` 所有 `llm_call`/`llm_provider_failed` 无 request_id`LlmRequest``request_id` 字段并从 API 层透传(违 ARCH §9.3 端到端追踪不变量) |
| CR-H5 🟠 `thinking` 字段:实现或删除 | ⬜ | @llm | — | `types.py:41` 声明但所有 adapter 忽略,`thinking=True` 静默无效、无报错;按 YAGNI 删除或实现转发 + 测试 |
| CR-H6 🟠 流式路径加 per-provider 重试 | ⬜ | @llm | — | `gateway.py:268` 流式仅 1 次尝试,首 token 前一次瞬时 429 烧光 fallback 链(与非流式 `_complete_with_retry` 不对称);用 `_retrying()` 包裹首 token 前尝试或显式文档化 |
| CR-H7 🟠 `TOOLBOX` 注册表不可变 | ⬜ | @backend | — | `packages/skills/ww_skills/toolbox_registry.py:70` 裸 dict 可变;仿同库 `SPECS``Final[Mapping]=MappingProxyType(...)`(违不可变不变量) |
| CR-H8 🟠 `.env.example` 移除可用 Fernet key | ⬜ | @devops | — | 已确认提交真实可用 key注释「可直接用于本地」换非功能占位符 + 突出轮换提示;联动 CR-C1 轮换 |
| CR-H9 🟠 API 文本输入加 `max_length` | ⬜ | @backend | — | `schemas/{providers,generation,projects}.py` `api_key`/`brief`/`final_text`/`segment``min_length=1`DoS/成本失控加上限api_key≤512、brief≤1e4、final_text≤2e5+ ASGI 请求体大小限 |
| CR-H10 🟠 前端 `errorCode` 去重 | ⬜ | @frontend | — | `lib/style/{useRefine,useStyleLearn}.ts` 各自重复定义,统一 import 自 `lib/generation/cards.ts:132`DRY 漂移隐患) |
| CR-H11 🟠 Drawer 关闭还原焦点 | ⬜ | @frontend | — | `components/Drawer.tsx:41` 关闭后焦点掉到 body违 WCAG 2.4.3);传 `triggerRef`、关闭时 `.focus()` |
| CR-H12 🟠 RefineView 取消在途 refine | ⬜ | @frontend | — | `components/style/RefineView.tsx:33` 无 AbortController段切换时旧响应可覆盖新响应`useRefine` 暴露 abort 在 effect cleanup 调用,去 blanket exhaustive-deps disable |
| CR-M1 🟡 后端架构整改批 | ⬜ | @backend | — | `outline.py:162` volume 下推 DB 过滤;`generation.py:326`/`toolbox.py:431` N+1 INSERT→`bulk_create``jobs.py:28` 裸 ORM→repo`provider_deps.py:91` 去私有 `_client`(加 `probe_connection()`);列表端点加分页。⚠️`assemble.py:155` 8 串行查询:**AsyncSession 不支持同一 session 并发,`gather` 须每查询独立 session**,不可照搬 |
| CR-M2 🟡 网关/adapters 整改批 | ⬜ | @llm | — | `gateway.py:90` `is_open()` 副作用违 CQS`pricing.py` 未知 model 静默记 0 → 加 warningadapter `str(exc)` 入异常消息可能带 vendor 响应体→只留类名+status |
| CR-M3 🟡 LangGraph 链健壮性 | ⬜ | @llm | — | `chain_runner.py:124` accept commit 后 `run_overdue_scan` 非幂等且失败无法 resume→部分进度空洞幂等化或纳入事务`chain_runner.py:159` 改用公开 `get_state().next` 替私有 `__interrupt__``chain/nodes.py:213` 链内 4 审串行→并行/复用 review 子图 |
| CR-M4 🟡 前端 key/a11y/cleanup 批 | ⬜ | @frontend | — | 可编辑列表去 index-key`CharacterGenerator.tsx:135`/`GeneratorRunner.tsx:178`/`ForeshadowCard.tsx:68`SSE `KNOWN_EVENTS``stop`(核对 `memory/contracts.md`ARIA 补全AppShell nav label / NavDrawer aria-controls / ForeshadowCard `<dl>` 结构ProjectWizard 步骤焦点;`ReviewReport.tsx:301` setTimeout 卸载清理 |
| CR-M5 🟡 前端 TS 边界类型收紧 | ⬜ | @frontend | — | `lib/api/server.ts:34` `parseJsonBody` 加最小结构校验;`useAccept.ts:60` `ApiErrorEnvelope` 从生成 schema 派生;`applyFix.ts` `hasApplicableFix` 改类型谓词去 `as string` |
| CR-L1 🟢 LOW 清理批 | ⬜ | @backend·@llm·@frontend | — | `chain.py:256` 静默回退加 warningSSE 端点补 OpenAPI `text/event-stream` 声明;冗余 `as`/`.valueOf()` cast 清理;测试脚本 `print`→logging |
| CR-D1 🟡 抬高后端依赖下界(可选升级评估) | ⬜ | @devops | — | `pyproject.toml` 下界过旧(`langgraph>=0.2.40`/`anthropic>=0.34`),抬到接近 lockfile 实锁版本保可复现;后端实锁版本均当前。可选评估 vitest 3.x / tailwind 4.x |
> **维度小结**:后端 FastAPI 架构 0C/2H/7M·Python 规范 0C/4H/6M·LangGraph 1C/2H/5M·前端 React 0C/2H/9M·前端 TS 0C/1H/8L·安全 0C/2H/4M·依赖 1C。后端依赖全部当前大版本线前端唯一关键缺口=Next RCE。
---
## 已封板T6 · 创作工具箱(通用生成器框架)✅
> **背景**对标竞品「创作工具箱」——把每个「XX 生成器」实现为声明式 skill`AgentSpec` 的设计本意),用一条**通用执行路径**驱动。用户已定:① 走**通用 skill 框架**(非逐个硬编码)② 封面/图像本期跳过 ③ 文本生成器全做(上架已有 3 + 新建 8。完整设计见 `~/.claude/plans/creation-toolbox-generators.md`。
> **P1 目标DoD**`GeneratorTool` 描述符 + `TOOLBOX` 注册表 + 通用 `GET /skills/toolbox` & `POST /projects/{id}/skills/{tool_key}/generate` 端点 + **1 个最简生成器(脑洞)端到端打通**mock 网关 E2E 零 token。证实「加生成器 = 加一份声明」这条路。
@@ -126,6 +161,7 @@ T0.1 monorepo 骨架 ✅ @devops · T0.2 16 MVP 表迁移无漂移users st
> 格式:`- [YYYY-MM-DD] @skill 完成/进展 Txx — 一句话结果 + 影响的契约/文件`
- [2026-06-26] @orchestrator**全项目 Code Review七维度完成 → `QA-CR` 整改批立项**。6 个专科 reviewer 并行(后端 FastAPI 架构 / Python 规范 / LangGraph 最佳实践 / 前端 React 架构 / 前端 TS 类型安全 / 应用安全)+ 依赖时效自查212 py + 168 tsx 全覆盖。绿门禁下仍揪出 **2 CRITICAL + 13 HIGH + 一批 MEDIUM/LOW****C1** Next.js 15.1.3 RCECVE-2025-66478「React2Shell」App Router+RSC 受影响,升 15.1.9/15.5.7/16.x + 轮换密钥);**C2** 多章链缺 `recursion_limit`(默认 25 supersteps、每章 4 节点→超 ~6 章即崩批量产卷不可用。HIGH 含 Kimi OAuth 占 DB 会话 / httpx 泄漏 / Anthropic 计费记 0 / request_id 未贯通 / `.env.example` 提交可用 Fernet key / 文本输入无 `max_length` / 前端 errorCode 重复 / Drawer 焦点 / RefineView 未取消。**确认无虞**SSRFbase_url 仅硬编码)/SQL 注入(全 ORM/XSS无 dangerouslySetInnerHTML/错误信封不泄密/Kimi device-flow 无 CSRF。已登记 `QA-CR` 任务表CR-C1/C2 + CR-H1..H12 + CR-M1..M5 + CR-L1 + CR-D1按 owner 分派 @frontend/@backend/@llm/@devops,落地顺序见当前阶段 background。**本批为评审立项,尚无代码改动。**
- [2026-06-23] @orchestrator**🎉 多章工作流链Chain Workflow交付 + 多 agent 交叉评审 + CRITICAL 修复 + 合并 developmerge `513bf71`**。对标星月「一键多章」+ 差异化「每章过四审、遇冲突才停人」。设计契约 `docs/design/chain-workflow.md`commit `5ee9799`)。**多 agent 逐学科建造**(契约先行、目录单写者):**C1 @llm**(`7f3eaab`) LangGraph **cyclic 链图** `orchestrator/chain/{state,nodes,graph}.py``build_chain_graph`write→review→decide→accept 循环 + **interrupt-on-conflict** + resume删死代码 `build_write_graph``write_node` 本体留用7 单测 mock 网关+fake session+MemorySaver·**C2 @backend**(`29349dc`) 服务/3 端点/schema/checkpointer 接线(见下条)·**C3 @db**(`548b7ab`) langgraph 检查点 4 表迁移 `d3e4f5a6b7c8``autocommit_block` 避开 `CREATE INDEX CONCURRENTLY` 与 alembic 事务死锁;`env.py` `include_object` 豁免这 4 表防漂移误报)·**C4 @qa**(`061792d`) E2E `tests/test_chain_workflow_e2e.py` 真 pg + mock 网关零 token无冲突两章全自动 + 冲突→interrupt→awaiting→resume→accept。**3 路对抗交叉评审**python/fastapi/不变量)在**绿门禁下仍揪出 1 CRITICAL + 7 HIGH**——证伪"测试过=没问题"CRITICAL=链把**绑请求 session 的 gateway** 传进 BackgroundTask后台跑时 session 已关→write/review 的 `usage_ledger.record()` 写进死 session **静默丢失**(链计费全丢,违 #1E2E 清了 ledger 却没断言其存在故漏掉)。**修复**(`d1ea83b`):网关改 `chain_gateway_builder` 按节点 session 重建(仿 `digest_gateway_builder`/`style.py`);日志脱敏(`_classify_job_error`+`exc_type`,去 `str(exc)`§5resume 原子 claim `JobRepo.claim_awaiting_to_running`(防并发双跑同 thread_id非 awaiting→409+ 所有权校验(`JobView.project_id`跨项目→404`ChainResumeAccepted`(去 start/count=0 哨兵);删死导入 `extract_conflicts`**补回归断言**(链 `usage_ledger` 必有行 + `chapter_reviews` 每章一行)。**出口DoD✅**:后端门禁绿 ruff/format 干净 · mypy **194 Success** · alembic **无漂移**(业务表零迁移,复用 `jobs.status=awaiting_input` + `result.awaiting_chapter`;唯一新 DDL=langgraph 检查点表)· pytest **608 passed**+6 回归测)。首次真正启用 LangGraph checkpointer/interrupt。守不变量 #1/#3/#4/#5/#9。**剩余下一步(未做)**:① 前端链 UI`pnpm gen:api` 纳入 run/resume + 发起页/进度轮询/awaiting 裁决续跑面板,复用 `ConflictAdjudication`);② §12 文档回写 `ARCHITECTURE.md §5.2`(补 cyclic 链图 + 更正现状:写章直连流式/单章 accept 为事务/链图首启 checkpointer③ 未 push 远端。可选:续写/扩写节点入链UGC 链市场需多租户deferred
- [2026-06-23] @backend**✅ Chain Workflow C2多章链 服务+端点+schema+checkpointer 接线,分支 `feat/chain-workflow`**。承 C1(@llm)链图(`build_chain_graph` §3.3),落地服务/端点壳:① **3 端点**`routers/chain.py``POST .../chains/{key}/run`→202 `ChainRunAccepted``POST .../chains/runs/{job_id}/resume`→202`GET /jobs/{id}` 复用。校验count 1..50→422、未知 chain_key→404、项目不存在→404、resume job 不存在→404、resume 非 awaiting→**409 新码 `CONFLICT`**、无凭据→503。② **schema**`schemas/chain.py``ChainRunRequest/ChainRunAccepted/ChainResumeRequest``ConflictDecision` 复用)。③ **服务**`services/chain_runner.py``run_chain_job` 仿 `run_job` 壳自建独立 session 驱动链图set_running→ainvoke→据 `__interrupt__``awaiting_input``done`/`failed``build_accept_op` 在 apps/api 装配验收事务闭包注入图节点,守 #3/#4token 不入 result/日志)+ `services/chain_deps.py``get_checkpointer_factory`:运行时 `AsyncPostgresSaver` 上下文 / 测试 MemorySaver。④ **零迁移**§7复用 `jobs`,新增 `status="awaiting_input"` + `JobRepo.set_awaiting`awaiting 章经 `result.awaiting_chapter`;新错误码 `ErrorCode.CONFLICT`(409)。⑤ **新网关缝**`project_deps.build_chain_gateway`/`get_chain_gateway`(按请求 tier writer/analyst/light 分派——单档网关恒返该档会错路由 review/digest见 gotcha+ `get_digest_gateway_builder`。**单测**`apps/api/tests/test_chain.py` 12 用例mock 网关 + MemorySaver + fake session/accept_op无 DB/无网络/无真 LLMrun→202、resume→202、未知 key 404、count 越界(>50/0) 422、resume 非 awaiting 409、项目/ job 不存在 404、run_chain_job 无冲突→done+written 满、冲突→awaiting→resume→done、错误脱敏、accept_op 冲突缺判→CONFLICT_UNRESOLVED。**全仓门禁绿**ruff/format 干净、mypy **193 Success**、alembic **无漂移**(无 ORM 变更、pytest **600 passed**。守不变量 #1/#3/#4/#5/#9。记 contracts C-Chain 稳定 + 2 decision + 3 gotcha。**唯一新增 DDLlanggraph 检查点表)= C3 迁移;前端链 UI = 契约稳定后 @frontend follow-up本期非目标。**
- [2026-06-22] @orchestrator**🎉 T6 创作工具箱(通用生成器框架)全工具箱交付 + 独立复核(分支 `feat/t6-creation-toolbox`**。多 agent 并行波次Wave 0@llm T6.5 ‖ @backend 描述符类型 ‖ P2 收尾)→ Wave A@backend T6.2+T6.3 TOOLBOX+端点合龙,契约稳定点)→ Wave B@frontend T6.6 ‖ @qa T6.4)→ Wave C 收口,目录单写者零写冲突、契约先行、零返工。**@llm T6.5**`ww_agents` +7 输出 schema + 7 specbook-title/blurb/name/golden-finger/glossary/opening/fine-outline只声明 tier #2+ `build_outline_chapter_context`with_outline_chapter 策略)。**@frontend T6.6**`gen:api` 纳入 3 端点;`app/projects/[id]/toolbox/page.tsx`(RSC) + `components/toolbox/{ToolboxPage,ToolCard,GeneratorRunner}` + `lib/toolbox/*`(声明驱动纯函数,按 `input_fields` 渲染表单、按 `output_kind` 渲染预览、可入库者复用 `ConflictAdjudication` 走 409→ack+ LeftNav「工具箱」+ ⌘K `nav-toolbox`/`action-gen-*`legacy 3 跳现页不回归。**@qa T6.4**`tests/test_t6_toolbox_e2e.py` 5 用例真 pg + mock 网关零 token**未发现端点 bug**。**P2 收尾**:限流→`decisions.md` 记延后单用户原型noopener/Committable 经查本分支早已修;`provider_deps.py` 残留 `type:ignore` 为 SDK 私有属性探活、load-bearing 保留。**最终独立复跑全仓门禁绿**:后端 ruff/format 干净、mypy **195 Success**、alembic **无漂移**descriptor 在代码、复用既有表零建表迁移、pytest **583 passed**;前端 gen:api/lint/tsc/vitest **279**/build OK。守不变量 #2(只声明 tier/#3(预览不写库、入库经验收 gate/#9system_prompt 进缓存前块)。**"加生成器=加一份声明" 经实证8 个新生成器复用同一通用执行器 + 同一前端组件)。** **实景 browse 复验(工具箱卡片栅格→脑洞预览→词条入库)待做。** 后续可选 P5封面图像轨道 / 自定义 skill 运行期 schema / 收敛已有 3 竖井。