docs(asl): Upgrade Tool 3 architecture from Fan-out to Scatter+Aggregator (v2.0)

Architecture transformation:
- Replace Fan-out (Manager->Child->Last Child Wins) with Scatter+Aggregator pattern
- API layer directly dispatches N independent jobs (no Manager)
- Worker only writes its own Result row, never touches Task table (zero row-lock)
- Aggregator polls groupBy for completion + zombie cleanup (replaces Sweeper)
- Reduce red lines from 13 to 9, eliminate distributed complexity

Documents updated (10 files):
- 08-Tool3 main architecture doc: v2.0 rewrite (schema, Task 2.3/2.4, red lines, risks)
- 08d-Code patterns: rewrite sections 4.1-4.6 (API dispatch, SingleWorker, Aggregator)
- 08a-M1 sprint: rewrite M1-3 core (Worker+Aggregator), red lines, acceptance criteria
- 08b-M2 sprint: simplify SSE (NOTIFY/LISTEN downgraded to P2 optional)
- 08c-M3 sprint: milestone table wording update
- New: Scatter+Polling Aggregator pattern guide v1.1 (Level 2 cookbook)
- New: V2.0 architecture deep review and gap-fix report
- Updated: ASL module status, system status, capability layer index

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-24 22:11:09 +08:00
parent 85fda830c2
commit 371fa53956
13 changed files with 1163 additions and 597 deletions

View File

@@ -0,0 +1,145 @@
# **🔍 深度审查与断层修复报告:工具 3 全文提取 (V2.0 散装架构版)**
**审查人:** 资深架构师
**审查对象:** 08 总纲及 08a/08b/08c/08d 拆分计划文档
**审查基准:** 《散装派发与轮询收口任务模式指南 v1.0》
**总体评估:** 战略方向极其正确,但**文档间存在严重的自相矛盾**。旧版 Fan-out 的代码Manager、原子递增、独立清道夫未被彻底清除导致架构撕裂。必须立即修复 08a、08b 和 08d 文档。
## **🚨 致命断层 1Worker 仍在修改父任务(行锁地雷未除)**
### **❌ 发现的问题 (位于 08d 代码手册 §4.3)**
在 08 总纲中您明确写道“Worker 绝不碰 Task 表,进度由 Aggregator groupBy 聚合”。
但是!在 08d 的 ExtractionChildWorker 代码示例中,依然保留了旧版 Fan-out 的致命逻辑:
// 08d 错误残留代码:
const \[\_resultUpdate, taskAfterUpdate\] \= await prisma.$transaction(\[
prisma.aslExtractionResult.update(...),
prisma.aslExtractionTask.update({ ... data: { successCount: { increment: 1 } } }) // ❌ 严重违规!
\]);
// ...
if (taskAfterUpdate.successCount \+ ... \>= totalCount) { ... } // ❌ 严重违规 (Last Child Wins 残留)
**危害:** 如果开发照抄这段代码,散装架构最核心的优势“无锁并发”将彻底丧失,依然会引发 Lock wait timeout 和死锁!
### **✅ 修复指令 (修改 08d §4.3)**
彻底删除 $transaction、删除 increment删除 Last Child Wins 判定。Worker 的代码必须极简:
// 修正后的 08d §4.3:纯粹的散装 Worker
// ... 幽灵重试守卫保持不变 ...
const extractResult \= await this.extractionService.extractOne(resultId, taskId);
// ✅ 核心:只管更新自己的 Result绝不碰 Task
await prisma.aslExtractionResult.update({
where: { id: resultId },
data: { status: 'completed', extractedData: extractResult.data, processedAt: new Date() }
});
// ✅ SSE 广播 (可选)
await broadcastLog(taskId, { source: 'system', message: \`✅ ${extractResult.filename} extracted\` });
// 结束!没有任何收口逻辑!
## **🚨 致命断层 2Manager 的幽灵依然存在**
### **❌ 发现的问题 (位于 08a M1-3 和 08d §4.2)**
* 08 总纲明确声明“消灭的组件ExtractionManagerWorker”“API 层直接散装派发 N 个独立 Job”。
* 然而08a (M1-3) 依然安排了 1 天工期去开发 ExtractionManagerWorker.ts。
* 08d (§4.2) 依然详细保留了 Manager Worker 的完整代码。
**危害:** 架构撕裂。如果 API 层发了一个 Manager JobManager 又去发 Child Job这就退回到了极其笨重的 Fan-out 模式,且徒增了一次重试崩溃的风险。
### **✅ 修复指令**
1. **修改 08a M1-3**:划掉 ExtractionManagerWorker.ts 的开发任务。
2. **修改 08d §4.2**:将此节更名为 **§4.2 API 层散装派发 (ExtractionController)**,并替换为以下代码:
// 修正后的 08d §4.2API 直接派发
async function createTask(req, reply) {
// ... DB 幂等拦截 (idempotencyKey) ...
// 1\. 获取 PKB 快照元数据并冻结到 DB (移至 API 层执行)
const pkbDocs \= await pkbBridge.getDocumentsDetail(documentIds);
const resultsData \= pkbDocs.map(doc \=\> ({
taskId: task.id,
pkbDocumentId: doc.documentId,
snapshotStorageKey: doc.storageKey,
status: 'pending'
}));
await prisma.aslExtractionResult.createMany({ data: resultsData });
const createdResults \= await prisma.aslExtractionResult.findMany({ where: { taskId: task.id } });
// 2\. 🚀 极速散装派发
const jobs \= createdResults.map(result \=\> ({
name: 'asl\_extract\_single',
data: { resultId: result.id, taskId: task.id, pkbDocumentId: result.pkbDocumentId },
options: { singletonKey: \`extract-${result.id}\`, expireInMinutes: 30 }
}));
await jobQueue.insert(jobs); // 批量压入 pg-boss
return reply.send({ taskId: task.id });
}
## **🚨 致命断层 3Sweeper(清道夫) 与 Aggregator(收口器) 未合并**
### **❌ 发现的问题 (位于 08a M1-3 和 08d §4.6)**
在《散装模式指南》中你们团队提出了一个绝妙的优化“Aggregator 兼职 Sweeper一个组件两个职责”。
但在 08a 中,依然要求单独注册 asl\_extraction\_sweeper在 08d 中也保留了独立的 Sweeper 代码。
### **✅ 修复指令 (修改 08a 和 08d)**
1. **修改 08a**:删除 Step D (Sweeper 注册)。新增任务:“开发 ExtractionAggregator.ts实现僵尸清理与轮询收口”。
2. **修改 08d**:删除 §4.6 的独立 Sweeper。补充 ExtractionAggregator 的标准代码:
// 修正后的 08d §4.6ExtractionAggregator 轮询包工头
jobQueue.work('asl\_extraction\_aggregator', async () \=\> {
const tasks \= await prisma.aslExtractionTask.findMany({ where: { status: 'processing' } });
for (const task of tasks) {
// 1\. 顺手清理僵尸 (Sweeper 职责)
await prisma.aslExtractionResult.updateMany({
where: { taskId: task.id, status: 'extracting', updatedAt: { lt: new Date(Date.now() \- 30 \* 60 \* 1000\) } },
data: { status: 'error', errorMessage: '\[Aggregator\] Timeout, likely worker crash.' }
});
// 2\. 聚合统计
const stats \= await prisma.aslExtractionResult.groupBy({ by: \['status'\], where: { taskId: task.id }, \_count: true });
const pending \= stats.find(s \=\> s.status \=== 'pending')?.\_count || 0;
const extracting \= stats.find(s \=\> s.status \=== 'extracting')?.\_count || 0;
// 3\. 收口
if (pending \=== 0 && extracting \=== 0\) {
await prisma.aslExtractionTask.update({ where: { id: task.id }, data: { status: 'completed' } });
}
}
});
## **⚠️ 体验断层 4M2-3 对 NOTIFY/LISTEN 的执念**
### **❌ 发现的问题 (位于 08b M2-3)**
08 总纲明确将 NOTIFY/LISTEN 跨 Pod 广播降级为“已被 V2.0 纯轮询替代/可选增强”。但在 M2 冲刺清单08b M2-3依然将 SseNotifyBridge.ts 和独立 PgClient 的开发列为了“必须做的额外任务”。
### **✅ 修复指令 (修改 08b)**
为了保证 M2 的按时交付,请将 08b 中的 **"🆕 v1.5 额外任务SSE 跨 Pod 广播"** 直接**删除**或标注为 **\[P2 可选\]**。
在散装架构下,既然进度条和跳页全靠 React Query 轮询,终端日志由于没有严重的业务关联,前期完全可以用“本 Pod 内存事件”将就,没必要在 M2 阶段去死磕复杂的 PG 独立监听连接。
## **🏁 架构师总结建议**
这个断层非常典型。因为我们前期对 Fan-out 模式进行了多达 6 轮的极限推演和打磨那些代码乐观锁、原子递增、Manager已经深入人心导致在重写 V2.0 文档时,大家潜意识里舍不得删掉它们。
**请项目经理做最后一步:**
打开 08a、08b 和 08d。凡是带了 Manager、successCount: { increment: 1 }、Last Child Wins、asl\_extraction\_sweeper 字眼的代码和任务,**全部无情删掉**,替换为上述的散装代码。
删完之后,你们的系统代码量将减少 30%,但稳定性将提升 200%!恭喜团队,你们即将拥有一套极其优雅的底层流水线,放心开干吧!