docs: QA-CR CRITICAL+HIGH 全部 收口——台账标完成 + 变更日志登记

This commit is contained in:
Yaojia Wang
2026-07-08 11:32:47 +02:00
parent dd6d2fce2b
commit 331a07abc4

View File

@@ -17,20 +17,20 @@
| 任务 | 状态 | 负责 | 依赖 | 备注 | | 任务 | 状态 | 负责 | 依赖 | 备注 |
|---|---|---|---|---| |---|---|---|---|---|
| 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-C1 🔴 升级 Next.js 修 RCE + React 补丁 | | @frontend | — | `f4c2c4e` next 15.1.3→**15.5.20**registry backport 线,避 16.x 大迁移、react/react-dom 19.0.0→**19.2.7**、eslint-config-next/@types 同步;`pnpm install` 重锁 + `pnpm build` 绿;`pnpm audit` next/react/react-dom 无 high/critical`lib/security/dependencyFloors.ts` 版本下界守卫。**⚠️ 运维待办(用户执行)**CVE 号无法对真库核验(升级后 `pnpm audit`/GitHub advisory 复核)+ 升级后轮换全部应用密钥 |
| 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-C2 🔴 多章链设 `recursion_limit` | | @backend | — | `ed0a679` **前提证伪**langgraph 1.2.5 默认上限=10007非 25、count≤50→~200 步,**无实活崩溃**;仍加显式**顶层** `recursion_limit=count*4+10`(防御硬化);测断言 config 键(行为「跑长链完成」测会 vacuous |
| CR-H1 🟠 Kimi OAuth 轮询不占 DB 会话 | | @backend | — | `services/job_runner.py:75`+`routers/kimi_oauth.py:85` 轮询全程(最坏 >200s占连接池连接、饿死其他请求轮询循环无需 DB仅最终 `upsert_oauth_credential` 开短会话 | | CR-H1 🟠 Kimi OAuth 轮询不占 DB 会话 | | @backend | — | `ca8fbc8` 轮询移出会话:`_poll_for_token`(无会话)+`_run_kimi_oauth_poll`,仅最终 persist 走 run_job 短会话;`job_runner` 未动(与 style_learn 共享) |
| 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-H2 🟠 修 `httpx.AsyncClient` 泄漏 | | @backend | — | `5dd336b`+`26f10d0` 请求级依赖改 `_http_client` 生成器(`async with``aclose`);两处测 override 跟随改,堵住 E2E 误触真网 |
| CR-H3 🟠 Anthropic 结构化输出记真实 usage | | @llm | — | `adapters/anthropic.py:161` 硬编码 `usage=0``usage_ledger` 全记 0 成本、腐蚀计费(违 ARCH §4.8);用 `create_with_completion`原始响应 usage | | CR-H3 🟠 Anthropic 结构化输出记真实 usage | | @llm | — | `cb8fddf` `create_with_completion` `raw.usage`,弃硬编码 0修计费腐蚀 |
| CR-H4 🟠 `request_id` 贯通网关日志 | | @llm | — | `gateway.py:183` 所有 `llm_call`/`llm_provider_failed` 无 request_id`LlmRequest``request_id` 字段并从 API 层透传(违 ARCH §9.3 端到端追踪不变量) | | CR-H4 🟠 `request_id` 贯通网关日志 | | @llm | — | `51fe9f4`+`e08b01a` LlmRequest 加可选 `request_id` + **条件透传**(仅非 None否则覆盖 contextvars 反退化 sync/SSE 追踪);`llm_call`+两处 `llm_provider_failed` 全锁测。契约 C1-扩 |
| CR-H5 🟠 `thinking` 字段:实现或删除 | | @llm | — | `types.py:41` 声明但所有 adapter 忽略,`thinking=True` 静默无效、无报错;按 YAGNI 删除或实现转发 + 测试 | | CR-H5 🟠 `thinking` 字段:实现或删除 | | @llm | — | `afeac51` 删死字段 `LlmRequest.thinking`YAGNI`Capabilities.thinking`(异名活字段);回写 ARCH/contracts |
| CR-H6 🟠 流式路径加 per-provider 重试 | | @llm | — | `gateway.py:268` 流式仅 1 次尝试,首 token 前一次瞬时 429 烧光 fallback 链(与非流式 `_complete_with_retry` 对称);用 `_retrying()` 包裹首 token 前尝试或显式文档化 | | CR-H6 🟠 流式路径加 per-provider 重试 | | @llm | — | `8a2b1b0` `_stream_with_retry` 对称 run仅首 token 前重试mid-stream 失败照抛(不变量 §4.5 |
| CR-H7 🟠 `TOOLBOX` 注册表不可变 | | @backend | — | `packages/skills/ww_skills/toolbox_registry.py:70` 裸 dict 可变;仿同库 `SPECS` `Final[Mapping]=MappingProxyType(...)`(违不可变不变量 | | CR-H7 🟠 `TOOLBOX` 注册表不可变 | | @backend | — | `25d48b7` `Final[Mapping]=MappingProxyType(_TOOLBOX_BY_KEY)`(仿 SPECS |
| CR-H8 🟠 `.env.example` 移除可用 Fernet key | | @devops | — | 已确认提交真实可用 key注释「可直接用于本地」换非功能占位符 + 突出轮换提示;联动 CR-C1 轮换 | | CR-H8 🟠 `.env.example` 移除可用 Fernet key | | @devops | — | `baa41d3` 换不可用占位符Fernet 拒绝→照抄即启动失败)+ 轮换提示。**⚠️ 真 key 仍在 git 历史,曾用该 key 的环境须轮换(用户)** |
| 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-H9 🟠 API 文本输入加 `max_length` | | @backend | — | `e1a24a2`+`d6b53d0` 全请求文本字段加上限api_key≤512/brief≤1e4/final_text≤2e5/segment≤2e4/directive≤2e4/instruction≤2e3…+ **2 MiB body 中间件 413 兜底** + `PAYLOAD_TOO_LARGE`。契约 C3`gen:api` 无漂移 |
| CR-H10 🟠 前端 `errorCode` 去重 | | @frontend | — | `lib/style/{useRefine,useStyleLearn}.ts` 各自重复定义,统一 import 自 `lib/generation/cards.ts:132`DRY 漂移隐患) | | CR-H10 🟠 前端 `errorCode` 去重 | | @frontend | — | `5665cc9` `lib/style/{useRefine,useStyleLearn}` 统一 import 自 `generation/cards` |
| CR-H11 🟠 Drawer 关闭还原焦点 | | @frontend | — | `components/Drawer.tsx:41` 关闭后焦点掉到 body违 WCAG 2.4.3);传 `triggerRef`、关闭时 `.focus()` | | CR-H11 🟠 Drawer 关闭还原焦点 | | @frontend | — | `6e2b478` 行为本分支早已修;提取 `lib/a11y/useRestoreFocus` + renderHook 回归测试 + DRYDrawer/ContextDrawer 共用) |
| CR-H12 🟠 RefineView 取消在途 refine | | @frontend | — | `components/style/RefineView.tsx:33` 无 AbortController段切换时旧响应可覆盖新响应`useRefine` 暴露 abort 在 effect cleanup 调用,去 blanket exhaustive-deps disable | | CR-H12 🟠 RefineView 取消在途 refine | | @frontend | — | `5c02792` useRefine 暴露 `abort`setState-free+ AbortController + aborted 守卫弃迟到响应RefineView cleanup 调用 + 真 deps |
| 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-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-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-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 子图 |
@@ -39,6 +39,8 @@
| CR-L1 🟢 LOW 清理批 | ⬜ | @backend·@llm·@frontend | — | `chain.py:256` 静默回退加 warningSSE 端点补 OpenAPI `text/event-stream` 声明;冗余 `as`/`.valueOf()` cast 清理;测试脚本 `print`→logging | | 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 | | CR-D1 🟡 抬高后端依赖下界(可选升级评估) | ⬜ | @devops | — | `pyproject.toml` 下界过旧(`langgraph>=0.2.40`/`anthropic>=0.34`),抬到接近 lockfile 实锁版本保可复现;后端实锁版本均当前。可选评估 vitest 3.x / tailwind 4.x |
> **[2026-07-08] CRITICAL + HIGH 全部 ✅ 落地**14 项 + 4 项低危收尾),多 agent 并行开发(@llm/@backend/@devops/@frontend 四簇串行整合,各 TDD 红→绿、逐项补回归测试)。**门禁全绿**:后端 ruff/format 干净 · mypy **229** · alembic **无漂移** · pytest **934**;前端 lint/tsc 干净 · vitest **669** · build OK · coverage **95.41%**。**14 项对抗式复核 0 problem**skeptic 逐条实证 test 非 vacuous。落地含 3 处前提证伪C2 非实活崩溃/H4 sync-SSE 已由 contextvars 贯通/H11 行为本分支已修)。**MEDIUM/LOWCR-M1..M5 / L1 / D1按计划择批后清理暂 ⬜。**
>
> **维度小结**:后端 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。 > **维度小结**:后端 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。
--- ---
@@ -182,6 +184,8 @@ T0.1 monorepo 骨架 ✅ @devops · T0.2 16 MVP 表迁移无漂移users st
> 格式:`- [YYYY-MM-DD] @skill 完成/进展 Txx — 一句话结果 + 影响的契约/文件` > 格式:`- [YYYY-MM-DD] @skill 完成/进展 Txx — 一句话结果 + 影响的契约/文件`
- [2026-07-08] @llm/@backend/@devops/@frontend**✅ QA-CR CRITICAL + HIGH 整改批全部落地**14 项 + 4 低危收尾)。多 agent 工作流:先 14 个只读 agent 逐项**扎根真实代码**产精确修复+测试规格(纠出 3 处台账前提漂移),再按 owner 四簇**串行整合**入单工作树(避免并发写树 + repo 级 `ruff format .` 竞态),各 TDD 红→绿、逐项补回归测试、逐 fix 小步提交,末尾 14 个 skeptic **对抗式复核**(实证每条 test 非 vacuous0 problem。**关键落地/纠偏**C1 next 15.1.3→**15.5.20**+react **19.2.7**`pnpm audit` 清、`dependencyFloors.ts` 下界守卫,**CVE 号无法对真库核验+密钥轮换为用户运维事项**C2 **前提证伪**langgraph 默认上限 10007 非 25无实活崩溃仍加显式顶层 `recursion_limit`H4 request_id **条件透传**(否则覆盖 contextvars 反退化H2 修请求级 httpx 泄漏时**揪出 E2E 会误触真网**override 跟随修H9 全文本字段上限+2 MiB body 中间件 413 兜底(契约 C3`gen:api` 无漂移H8 `.env.example` 换不可用占位符(**真 key 仍在 git 历史,曾用环境须轮换**)。**门禁全绿**:后端 mypy 229/pytest **934**/alembic 无漂移;前端 vitest **669**/build/coverage 95.41%。契约 C1-扩LlmRequest.request_id+ C3H9 caps。**MEDIUM/LOW 择批后清理。** 提交 `cb8fddf``dd6d2fc`19 提交)。
- [2026-07-08] @backend**✅ QA-CR 后端整改 5 项TDD逐 fix 提交)**。**CR-C2**`chain_runner.run_chain_job``graph.ainvoke` 传显式 top-level `recursion_limit=count*NODES_PER_CHAPTER+HEADROOM`(防御硬化,命名常量)。**CR-H1**Kimi OAuth 设备轮询移出 DB 会话——新增 `_poll_for_token`(纯 HTTP)/`_make_persist_work`/`_run_kimi_oauth_poll`,仅最终持久化经 `run_job` 开短会话(防连接池饥饿;删 `_make_poll_work`)。**CR-H2**:请求级 httpx 客户端改**生成器依赖** `_http_client``async with ... yield`,请求结束 aclose 修泄漏);`_default_http_client` 保留供后台裸调E2E + 端点测试 override 键跟随改 `_http_client`。**CR-H7**:冻结 `TOOLBOX` 为只读 `MappingProxyType``_TOOLBOX_BY_KEY` 私有底 dict + `Final[Mapping]`,仿 SPECS。**CR-H9**:用户请求字符串字段补 `max_length`providers/generation/projects/style/foreshadow/rules/templates命名常量无魔数样本列表长度+每条双上界)+ 新错误码 `ErrorCode.PAYLOAD_TOO_LARGE`(413) + 应用级 `body_size_limit_middleware`(2 MiB按 Content-Length 拦截自建 413 信封)`main.py` 注册。**门禁全绿**ruff/format 干净 · mypy **229 文件**0 错)· pytest **928**+23recursion_limit 1 / poll-no-session 1 / http-gen 1 / toolbox-readonly 1 / input-size 19[17 参数化 + samples 1 + body413 1])· **无 DDL/无迁移**。记 contracts C3CR-H9新增 `maxLength` + `PAYLOAD_TOO_LARGE`)。→ **@frontend`pnpm gen:api`**(请求字段现有 `maxLength`;本 cluster 未代跑)。守不变量 #3/#8/#9 - [2026-07-08] @backend**✅ QA-CR 后端整改 5 项TDD逐 fix 提交)**。**CR-C2**`chain_runner.run_chain_job``graph.ainvoke` 传显式 top-level `recursion_limit=count*NODES_PER_CHAPTER+HEADROOM`(防御硬化,命名常量)。**CR-H1**Kimi OAuth 设备轮询移出 DB 会话——新增 `_poll_for_token`(纯 HTTP)/`_make_persist_work`/`_run_kimi_oauth_poll`,仅最终持久化经 `run_job` 开短会话(防连接池饥饿;删 `_make_poll_work`)。**CR-H2**:请求级 httpx 客户端改**生成器依赖** `_http_client``async with ... yield`,请求结束 aclose 修泄漏);`_default_http_client` 保留供后台裸调E2E + 端点测试 override 键跟随改 `_http_client`。**CR-H7**:冻结 `TOOLBOX` 为只读 `MappingProxyType``_TOOLBOX_BY_KEY` 私有底 dict + `Final[Mapping]`,仿 SPECS。**CR-H9**:用户请求字符串字段补 `max_length`providers/generation/projects/style/foreshadow/rules/templates命名常量无魔数样本列表长度+每条双上界)+ 新错误码 `ErrorCode.PAYLOAD_TOO_LARGE`(413) + 应用级 `body_size_limit_middleware`(2 MiB按 Content-Length 拦截自建 413 信封)`main.py` 注册。**门禁全绿**ruff/format 干净 · mypy **229 文件**0 错)· pytest **928**+23recursion_limit 1 / poll-no-session 1 / http-gen 1 / toolbox-readonly 1 / input-size 19[17 参数化 + samples 1 + body413 1])· **无 DDL/无迁移**。记 contracts C3CR-H9新增 `maxLength` + `PAYLOAD_TOO_LARGE`)。→ **@frontend`pnpm gen:api`**(请求字段现有 `maxLength`;本 cluster 未代跑)。守不变量 #3/#8/#9
- [2026-07-06] @backend/@llm/@frontend**✅ 灵感⑦ 群像按定位配比·缩减版 + §0 归属纠偏develop 直提)**。角色群像生成支持「按定位配比」下发。**契约变更** `CharacterGenerateRequest``role_mix:dict[str,int]|None`——`model_validator(mode="after")` 定义与既有 `count` 的关系在场时校验非空、每项≥1、总和≤`MAX_GENERATE_COUNT`=12并把 `count` 归一为各值之和(构造期归一化,忽略客户端 count缺省退回单一 `role`+`count`。**§0 归属纠偏**`build_character_gen_context`+`run_character_gen``ww_core/orchestrator/generation_node.py`)确为 **@llm 域**——加末位默认 `role_mix=None` 关键字参,在场时注入「角色定位配比」块(保插入序、确定性、无时间戳,守 #6/#7 assemble 纯函数;覆盖单一 role 行);`routers/generation.py` 穿 `role_mix=body.role_mix``character-gen.md` 加「按配比严格分配 role、各定位之和=总数」纪律 → **regen `prompt_hashes.json`**(仅 character-gen 哈希变,**不进 SPECS→不 bump 计数**)。**前端** `cards.ts``sanitizeRoleMix`(去空/去重/clamp≥1/按 12 截断)+`roleMixTotal`+`buildCharacterGenerateRequest` 第 4 参 roleMix`useCharacterGen` `CharacterGenInput.roleMix` 穿参;新 `RoleMixEditor.tsx` + `CharacterGenerator` SegmentedControl 单一/配比双模。**TDD**schema 校验 7 例总和≤12/空 dict/非正值拒绝)+ context 注入 2 例 + run 穿参 1 例 + 前端 cards 构造/clamp 5 例 + hook renderHook 1 例。**门禁全绿**:后端 ruff/format 干净 · mypy **222** · pytest **858**+11· 无迁移(未动模型,未跑 alembic前端 gen:api/lint/typecheck 干净 · vitest **543**+6· coverage 95%cards.ts 100%)· build OK。记 contracts C3。守不变量 #2/#6/#7/#9。**无 DB 迁移**。 - [2026-07-06] @backend/@llm/@frontend**✅ 灵感⑦ 群像按定位配比·缩减版 + §0 归属纠偏develop 直提)**。角色群像生成支持「按定位配比」下发。**契约变更** `CharacterGenerateRequest``role_mix:dict[str,int]|None`——`model_validator(mode="after")` 定义与既有 `count` 的关系在场时校验非空、每项≥1、总和≤`MAX_GENERATE_COUNT`=12并把 `count` 归一为各值之和(构造期归一化,忽略客户端 count缺省退回单一 `role`+`count`。**§0 归属纠偏**`build_character_gen_context`+`run_character_gen``ww_core/orchestrator/generation_node.py`)确为 **@llm 域**——加末位默认 `role_mix=None` 关键字参,在场时注入「角色定位配比」块(保插入序、确定性、无时间戳,守 #6/#7 assemble 纯函数;覆盖单一 role 行);`routers/generation.py` 穿 `role_mix=body.role_mix``character-gen.md` 加「按配比严格分配 role、各定位之和=总数」纪律 → **regen `prompt_hashes.json`**(仅 character-gen 哈希变,**不进 SPECS→不 bump 计数**)。**前端** `cards.ts``sanitizeRoleMix`(去空/去重/clamp≥1/按 12 截断)+`roleMixTotal`+`buildCharacterGenerateRequest` 第 4 参 roleMix`useCharacterGen` `CharacterGenInput.roleMix` 穿参;新 `RoleMixEditor.tsx` + `CharacterGenerator` SegmentedControl 单一/配比双模。**TDD**schema 校验 7 例总和≤12/空 dict/非正值拒绝)+ context 注入 2 例 + run 穿参 1 例 + 前端 cards 构造/clamp 5 例 + hook renderHook 1 例。**门禁全绿**:后端 ruff/format 干净 · mypy **222** · pytest **858**+11· 无迁移(未动模型,未跑 alembic前端 gen:api/lint/typecheck 干净 · vitest **543**+6· coverage 95%cards.ts 100%)· build OK。记 contracts C3。守不变量 #2/#6/#7/#9。**无 DB 迁移**。