Files
AIclinicalresearch/docs/09-架构实施/工具3全量代码级同步审计与修正清单.md
HaHafeng 85fda830c2 feat(ssa): Complete Phase V-A editable analysis plan variables
Features:
- Add editable variable selection in workflow plan (SingleVarSelect + MultiVarTags)
- Implement 3-layer flexible interception (warning bar + icon + blocking dialog)
- Add tool_param_constraints.json for 12 statistical tools parameter validation
- Add PATCH /workflow/:id/params API with Zod structural validation
- Implement synchronous parameter sync before execution (Promise chaining)
- Fix LLM hallucination by strict system prompt constraints
- Fix DynamicReport object-based rows compatibility (R baseline_table)
- Fix Word export row.map error with same normalization logic
- Restore inferGroupingVar for smart default variable selection
- Add ReactMarkdown rendering in SSAChatPane
- Update SSA module status document to v3.5

Modified files:
- backend: workflow.routes, ChatHandlerService, SystemPromptService, FlowTemplateService
- frontend: WorkflowTimeline, SSAWorkspacePane, DynamicReport, SSAChatPane, ssaStore, ssa.css
- config: tool_param_constraints.json (new)
- docs: SSA status doc, team review reports

Tested: Cohort study end-to-end execution + report export verified
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 13:08:29 +08:00

148 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# **🔬 工具 3 终极代码级同步审计与修正清单 (基于 Fan-out v1.2)**
**审计背景:** 确保《工具 3 开发计划 (v1.4.2)》及其代码模式08d完全、无死角地落实了《分布式 Fan-out 开发指南 v1.2》中的所有极端场景防御策略。
**审计结论:** 理论已同步,但**代码落地存在 4 处断层**。必须修改 08d-代码模式与技术规范.md 中的具体代码片段。
## **🚨 审计点 1Child Worker 临时错误重试的“死锁穿透” (必须修改)**
**🔍 逐行审查发现:**
在 08d 文档的 §4.3 ExtractionChildWorker 的 catch 块中,针对临时错误的代码目前是:
// 当前 08d 代码
// 临时错误 (429/网络抖动):直接 throw让 pg-boss 自动指数退避重试
throw error;
**💥 业务危害:**
这直接违背了 Fan-out 指南 v1.2 的核心补丁!因为上方使用了 updateMany 乐观锁把 AslExtractionResult 的状态改为了 extracting。如果直接 throwpg-boss 在 10 秒后重试时,数据库里该行还是 extracting乐观锁 updateMany 会返回 count: 0导致 Worker 误以为任务已完成而直接 return success。**最终导致父任务 AslExtractionTask 永远少一个计数,彻底卡死在 processing。**
**✅ 代码修正指令:**
必须在 ExtractionChildWorker 的 catch 块末尾throw error 之前,强制释放当前业务表的锁:
// 修正后的 08d §4.3 代码
} catch (error) {
if (isPermanentError(error)) {
// 致命错误处理逻辑不变...
return { success: false };
}
// ⚡ 必须增加的解锁代码:临时错误退避前,回退状态为 pending
await prisma.aslExtractionResult.update({
where: { id: resultId },
data: { status: 'pending' }
});
// 让出状态后,再抛出异常让 pg-boss 重试
throw error;
}
## **🚨 审计点 2Manager Worker 空文献的“无头挂起” (必须修改)**
**🔍 逐行审查发现:**
在 08d 文档的 §4.2 ExtractionManagerWorker 中,代码是:
// 当前 08d 代码
const results \= await prisma.aslExtractionResult.findMany({ where: { taskId: task.id } });
for (const result of results) {
await pgBoss.send('asl\_extraction\_child', ...);
}
// Manager 退出
**💥 业务危害:**
在工具 3 的业务流中,如果用户在 Step 1 勾选的 PKB 文献因为某种原因(如被其他协作者删除)导致 results.length \=== 0Manager 会直接退出。因为没有任何 Child 被派发Last Child Wins 永远不触发AslExtractionTask 状态永远是 processing前端进度条永远转圈。
**✅ 代码修正指令:**
在 ExtractionManagerWorker 获取到 results 后,必须增加边界拦截:
// 修正后的 08d §4.2 代码
const results \= await prisma.aslExtractionResult.findMany({ where: { taskId: task.id } });
// ⚡ 必须增加的空集合守卫
if (results.length \=== 0\) {
await prisma.aslExtractionTask.update({
where: { id: task.id },
data: { status: 'completed', completedAt: new Date() }
});
// 触发 SSE 完成事件
await prisma.$executeRaw\`SELECT pg\_notify('asl\_extraction\_sse', '{"taskId":"${task.id}","type":"complete"}')\`;
return;
}
// 正常循环派发...
## **🚨 审计点 3工具 3 专属 Sweeper 的缺位 (必须新增)**
**🔍 逐行审查发现:**
《Fan-out 开发指南 v1.2》规定必须有 Sweeper 清道夫。但在《工具 3 开发计划》的所有 Task 清单M1/M2/M3**完全没有分配开发 Sweeper 的任务**。
**💥 业务危害:**
如果没有针对 AslExtractionTask 写具体的清道夫代码,一旦遇到极度变态的 PDF 导致 MinerU 或 pymupdf4llm 的 Node.js 宿主进程 OOM 崩溃,该 Task 会永久挂起在前端工作台,医生无法进行后续操作。
**✅ 代码修正指令:**
必须在后端模块初始化时(如 backend/src/modules/asl/extraction/index.ts专门为工具 3 注册一个清道夫 Worker
// ⚡ 必须在工具 3 模块启动时注册
async function aslExtractionSweeper() {
const stuckTasks \= await prisma.aslExtractionTask.findMany({
where: {
status: 'processing',
// 工具 3 独有逻辑:使用 updatedAt 判断最后活跃时间超 2 小时
updatedAt: { lt: new Date(Date.now() \- 2 \* 60 \* 60 \* 1000\) },
},
});
for (const task of stuckTasks) {
await prisma.aslExtractionTask.update({
where: { id: task.id },
data: { status: 'failed', errorMessage: 'System timeout (OOM/Crash)', completedAt: new Date() },
});
}
}
await jobQueue.schedule('asl\_extraction\_sweeper', '\*/10 \* \* \* \*');
await jobQueue.work('asl\_extraction\_sweeper', aslExtractionSweeper);
## **🚨 审计点 4SSE 广播代码的安全隐患 (必须修改)**
**🔍 逐行审查发现:**
在 08d 的 §4.3 中Child Worker 完成提取后,依然在使用:
// 当前 08d 代码
this.sseEmitter.emit(taskId, { type: 'log', data: { ... } });
**💥 业务危害:**
这还是单机内存的 EventEmitter在 SAE 多实例(多 Pods部署下Pod A 上的用户绝对收不到 Pod B 产生的日志。前端日志流会严重断裂。
**✅ 代码修正指令:**
必须将所有 this.sseEmitter.emit 替换为安全的、截断的、参数化的 pg\_notify SQL 注入免疫调用:
// 修正后的 08d §4.3 代码
const logEntry \= { source: 'system', message: \`✅ ${extractResult.filename} extracted\` };
const payloadStr \= JSON.stringify({ taskId, type: 'log', data: logEntry });
// ⚡ 必须进行的 7000 bytes 安全截断(防 PostgreSQL 报错)
const safePayload \= payloadStr.length \> 7000 ? payloadStr.substring(0, 7000\) \+ '..."}' : payloadStr;
// ⚡ 必须使用的参数化 pg\_notify
await prisma.$executeRaw\`SELECT pg\_notify('asl\_extraction\_sse', ${safePayload})\`;
## **🏁 架构师最终放行许可**
只要开发团队在编写工具 3 代码时,把上述 **4 段具体的代码** 替换到工程中:
1. Child Worker catch 释放锁
2. Manager Worker 拦截空数组
3. 注册 asl\_extraction\_sweeper
4. 替换 sseEmitter 为参数化 pg\_notify
您的系统在抗压能力、容错能力和数据一致性上,将绝对达到顶尖大厂的微服务水准!**这一次,您可以 100% 放心闭眼放行了!祝团队开发顺利!**