scheduler-gateway/docs/PHASE-1-UNIFIED-TASK.md
Liuxinyu176 bbb364cd69 feat: DSH 调度网关(互联网关)v6.0.0 — 四模型合并版 R1+R2+R3
- 零第三方依赖,Node >=20 原生 ESM
- 团队式编排引擎:拆解/路由/执行/审查/合并全真实 LLM
- 阶段心跳、单一权威清单守卫、all-keys-failed 如实上报
- H4 会话视图/amend/watchdog 有界重试/产物区 artifacts.json
- H5 零依赖三栏控制台
2026-10-09 23:20:27 +08:00

399 lines
18 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.

# 调度网关第一阶段统一开发任务书
**任务书版本**:1.0
**发布日期**:2026-09-09
**适用项目**:`scheduler-gateway-epoch` 6.0.0
**执行方式**:四个模型同时执行同一份任务书
**阶段名称**:P0 生产化基线
## 一、任务目标
在不破坏现有网关、节点、团队编排、控制台、插件和测试能力的前提下,把当前单机原型的核心任务生命周期改造成可验证的工程化基础:
1. 任务状态只能通过受控状态机迁移。
2. 存储访问通过 Repository 接口隔离,现有文件存储行为保持兼容。
3. claim、settle、retry、cancel、create 等关键操作支持幂等和租约。
4. 进程、节点和执行器异常后,任务不会永久丢失、重复完成或无限重试。
5. 所有关键行为有结构化审计和测试证据。
本阶段**不做数据库迁移、不做多租户、不做 SSH 真实接入、不重写团队编排器、不开发新模型适配器**。
## 二、当前代码基线
重点代码:
```text
src/server.js HTTP 网关、节点协议、任务 API
src/store.js 任务/节点/审计/事件存储
src/scheduler-core.js 调度、领取、结算、重试、死信
src/protocol.js 状态常量、节点协议、鉴权
src/wal.js JSONL WAL
src/recovery.js 崩溃恢复
src/team/orchestrate.js 团队式编排主流程
src/team/panel-server.js 团队控制台 API
test/live-suite.mjs 网关、节点、调度、安全、WAL、压测测试
test/team-section.mjs 团队编排和证据链测试
```
现有行为、接口和测试是兼容基线。除非本任务书明确要求,不得删除、改名或改变既有 API 语义。
## 三、统一技术约束
### 3.1 必须遵守
- Node.js >= 20,原生 ESM,零第三方运行时依赖。
- 只使用 ASCII 新增代码和注释;已有中文文件按原编码维护。
- 不得修改 `归档-*`、`out`、`work`、`evidence` 中已有历史产物。
- 不得把密钥写入日志、测试快照、证据文件、任务结果、argv 或错误消息。
- 不得通过任意 `task.state = ...` 绕过状态机。
- 不得为了让测试通过而静默吞掉错误、伪造 REAL 结果或扩大重试上限。
- 保留现有 REAL/OFFLINE/fixture 诚实标记。
- 生产默认配置不能继续依赖 `dev-token-change-me`。
### 3.2 推荐实现方式
- 新增模块优先放在 `src/domain`、`src/application`、`src/adapters`、`src/platform`。
- 先包裹旧实现,再逐步迁移调用方;不要一次性重写 `server.js` 或 `orchestrate.js`。
- 错误使用稳定错误码,错误消息可以变化但错误类型不能靠字符串猜测。
- 所有变更都必须有针对性测试;跨模块改动必须补集成测试。
## 四、统一交付范围
### 任务 A:实现任务状态机
新增任务状态机模块,例如:
```text
src/domain/task-state-machine.js
```
要求:
- 定义当前任务所有合法状态和迁移。
- 提供 `canTransition(from, to)`、`transition(task, to, context)` 等明确接口。
- 统一处理 `queued`、`running`、`done/succeeded`、`failed`、`canceled`、`dead`、`rework`、`waiting_approval` 等当前代码实际使用的状态。
- 迁移失败必须抛出稳定错误码,例如 `INVALID_STATE_TRANSITION`。
- 迁移时追加历史记录,记录操作者、原因、时间、attemptId 和 requestId。
- 将 `server.js`、`store.js`、`scheduler-core.js` 中直接修改状态的关键路径迁移到状态机。
- 重复 settle、过期 lease settle、已取消任务回报等场景必须拒绝或转为幂等成功,不能产生第二个终态。
### 任务 B:建立 Repository 抽象
新增存储接口,例如:
```text
src/domain/repositories.js
src/adapters/storage/file-repository.js
```
要求:
- 至少抽象任务、节点、事件、审计、死信和产物索引访问。
- 现有 `GatewayStore` 可作为兼容实现,但业务服务不得继续依赖 Map 的内部结构。
- 保留 `persist:false` 的干净内存测试模式。
- 保留现有 JSON 落盘和 WAL 能力,不能因抽象而关闭恢复逻辑。
- Repository 方法必须区分“找不到”“冲突”“非法状态”“存储失败”。
- 为未来 SQLite/PostgreSQL 实现保留异步接口,即使当前 FileRepository 内部仍是同步实现。
建议最小接口:
```text
getTask(id)
createTask(input, context)
updateTask(id, patch, context)
transitionTask(id, nextState, context)
claimTask(id, lease, context)
settleTask(id, result, context)
listTasks(query)
getNode(id)
upsertNode(input)
appendEvent(event)
appendAudit(entry)
```
### 任务 C:实现幂等键和租约
要求:
- 创建任务支持 `Idempotency-Key` 或等价请求字段;同一项目/网关范围内重复请求返回同一任务。
- claim 生成唯一 `leaseId`,并记录 `leaseExpiresAt`、`nodeId`、`attemptNo`。
- settle 必须校验 lease;重复提交同一 `leaseId` 和相同结果应幂等返回;不同结果必须拒绝并审计。
- lease 过期后任务可重投,但旧节点迟到回报不得覆盖新结果。
- retry、cancel、approve、amend 具备幂等行为。
- 所有幂等命中和冲突都要有审计记录。
- 不得用进程内 Map 作为唯一幂等数据来源;至少要能随现有持久化状态恢复。
### 任务 D:统一错误、审计和请求关联
要求:
- 新增稳定错误码集合,例如:
```text
INVALID_ARGUMENT
TASK_NOT_FOUND
INVALID_STATE_TRANSITION
LEASE_NOT_FOUND
LEASE_EXPIRED
IDEMPOTENCY_CONFLICT
TASK_ALREADY_TERMINAL
STORAGE_ERROR
```
- 每个 HTTP 请求生成或透传 `X-Request-Id`。
- 每个任务运行生成或透传 `runId`、`taskId`、`attemptId`、`nodeId`。
- 审计记录至少包含:时间、动作、对象、结果、操作者/来源、requestId、runId、错误码。
- API 错误统一为:
```json
{
"error": {
"code": "INVALID_STATE_TRANSITION",
"message": "任务当前不可取消",
"requestId": "req_xxx",
"details": {}
}
}
```
- 保持现有接口状态码兼容;如必须变化,在兼容字段中同时提供新错误结构。
### 任务 E:健康检查和配置校验
新增:
```text
src/platform/config.js
src/platform/health.js
```
要求:
- 统一读取端口、host、数据目录、WAL、节点 token、TLS、LLM provider 和超时配置。
- 新增 `gateway check` 或等价检查命令。
- 新增 `/healthz`:进程存活检查,不依赖外部服务成功。
- 新增 `/readyz`:检查数据目录、WAL/Repository 可写性、必要配置和依赖状态。
- 生产模式检测到默认节点 token 时必须给出明确警告;安全模式可直接拒绝启动。
- 检查结果必须脱敏,不能输出 key、token、密码或完整环境变量。
### 任务 G:任务版本与乐观锁
当前 `task.state` 直接赋值,没有 revision 字段,幂等键只能去重但做不了并发冲突检测。
要求:
- 每个任务带 `revision`(整数,初始 1),每次 transition/settle/update 成功后自增。
- `transitionTask`、`settleTask`、`updateTask` 必须接受 `expectedRevision`;不匹配则返回 `REVISION_CONFLICT`,不修改任务。
- 重复提交相同 `leaseId` + 相同结果时,revision 不变,幂等返回当前终态。
- 不同结果或不同 `attemptId` 的 settle 必须因 revision/lease 校验失败而拒绝。
- revision 必须随任务持久化,重启后恢复的 revision 与落盘一致。
- 审计记录每次 revision 变化,包含旧 revision、新 revision、触发动作。
测试必须覆盖:
- 两个并发 settle 只有一个成功。
- 过期 lease 持有者迟到 settle 被拒绝。
- `expectedRevision` 不匹配时返回冲突错误且任务不变。
### 任务 H:事件持久化与 SSE 重连
当前 `EventBus` 是进程内 `Set`,seq 不持久化,SSE 断线后无法补播,审计链路不完整。
要求:
- 事件通过 `EventRepository` 持久化(FileRepository 先落 JSONL,与 WAL 同目录或 `state/events.jsonl`)。
- 每个事件带 `seq`(全局递增)、`type`、`aggregateId`(taskId/runId/nodeId)、`revision`、`at`、`requestId`。
- SSE 支持 `Last-Event-ID`:客户端断线重连时,从 `Last-Event-ID + 1` 开始补播。
- 没有 `Last-Event-ID` 时从当前 seq 开始推送(不回放全量)。
- 事件保留期可配置(默认 7 天或 10000 条,先到先裁);裁剪时保留最早和最新各一份用于边界测试。
- 进程重启后 seq 必须继续递增,不能回退。
测试必须覆盖:
- 断线重连后能收到断线期间的事件。
- 高并发下 seq 单调递增无重复。
- 损坏的事件文件不阻塞启动(跳过损坏行并审计)。
### 任务 I:回调机制加固
当前 `fireCallback` 是 best-effort 裸 `fetch` + 5s 超时,无重试、无签名、无死信,生产环境会丢任务结果。
要求:
- 回调必须有 HMAC-SHA256 签名:`X-Signature: t=<timestamp>,v1=<hmac>`,密钥从任务 `callbackSecret` 或网关 `GW_CALLBACK_SECRET` 读取。
- 回调失败按指数退避重试(至少 3 次:1s / 5s / 30s);全部失败后进入回调死信队列。
- 回调死信队列独立于任务死信队列;死信记录 `taskId`、`callbackUrl`、`lastError`、`attemptCount`、`createdAt`。
- 回调必须幂等:接收方可用 `X-Callback-Id`(= taskId)去重。
- 回调 payload 必须包含 `taskId`、`runId`、`state`、`revision`、`result`、`finishedAt`。
- 密钥不得出现在日志、审计、导出或错误消息中。
测试必须覆盖:
- 回调成功且签名校验通过。
- 回调失败后重试 3 次进入死信。
- 回调密钥缺失时明确报错而非静默跳过。
- 重复回调不会触发多次下游副作用(通过幂等键验证)。
### 任务 J:任务取消传播
当前 `cancel` 只改任务状态,不通知执行节点中断,节点可能继续跑已取消的任务。
要求:
- `cancel` 必须将取消信号传播到持有当前 lease 的执行节点(通过 `gw-node/1` 协议新增 `cancel` 消息或复用现有 `interrupt` 机制)。
- 取消后任务进入 `canceled` 终态;执行节点的迟到回报在任务已 `canceled` 时必须被拒绝。
- 如果任务有子任务(`parentRun` / `dependencies`),取消父任务时必须级联取消未完成的子任务。
- 取消操作本身必须幂等:对已 `canceled` 的任务再次 cancel 不报错。
- 取消必须审计:记录谁取消、为什么取消、取消传播到了哪些子任务。
- 如果节点不在线或取消信号投递失败,任务仍应进入 `canceled` 终态(取消不依赖节点确认)。
测试必须覆盖:
- 取消正在执行的任务后,节点的迟到回报被拒绝。
- 取消有子任务的父任务后,子任务也进入 `canceled`。
- 对已取消的任务再次调用 cancel 不报错。
- 节点离线时 cancel 仍能成功终态化任务。
### 任务 K:死信队列加固
当前 `deadLetter` 是普通数组,无 retryCount、无 originalTaskId、无 deadReason,死信可被无限重投。
要求:
- 每条死信记录必须包含:`deadLetterId`、`taskId`、`originalTaskId`(= 首次进入死信的任务 id)、`deadReason`、`retryCount`(从死信重投的次数)、`firstDeadAt`、`lastDeadAt`。
- 死信重投有上限:`maxDeadRetries`(默认 3);超过后标记 `exhausted`,不再自动重投。
- `exhausted` 死信只能通过人工 `POST /api/deadletter/:id/force-retry` 重投,且必须记录操作者。
- 死信队列支持按 `deadReason` / `retryCount` / `originalTaskId` 查询。
- 死信重投必须生成新的 `attemptId` 和 `leaseId`,不能复用旧 lease。
- 死信归档:`exhausted` 超过保留期后自动归档到 `state/archived-dead-letters.json`,归档后不再出现在活跃死信查询中。
测试必须覆盖:
- 任务失败 3 次进入死信。
- 死信重投超过 `maxDeadRetries` 后标记 `exhausted`。
- `force-retry` 能重投 `exhausted` 死信并记录操作者。
- 归档后的死信不出现在活跃列表中。
### 任务 F:补齐测试和验证脚本
新增或扩展测试:
- 状态机:合法迁移、非法迁移、重复终态、取消竞态。
- Repository:内存态、文件态、WAL 开关、损坏文件、恢复后读取。
- 幂等:重复创建、重复 claim、重复 settle、冲突 settle、过期 lease。
- 故障:进程中断、节点掉线、旧节点迟到回报、回调失败、磁盘不可写。
- 安全:错误响应不泄漏密钥,requestId 和审计不泄漏敏感字段。
- 回归:现有 `npm test`、`npm run test:conversation`、`npm run e2e-team` 能继续运行。
## 五、明确不允许的实现结果
- 只新增函数但没有接入真实任务路径。
- 只修改测试 fixture,未覆盖 server/node/orchestrator 实际入口。
- 通过放宽断言、增加无限重试、忽略异常来制造全绿。
- 把 `done`、`failed`、`dead` 等终态重新改回可执行状态但不留下 retry/rework 记录。
- 用文件名、日志文本或前端状态推断任务真实状态。
- 把数据库、Redis、消息队列作为本阶段必须依赖,导致现有零依赖模式无法运行。
- 修改归档文件或清理现有 evidence 以掩盖回归。
- 回调重试无限循环或吞掉签名错误;回调死信队列静默丢弃而不审计。
- 取消操作只改状态不传播到执行节点和子任务,导致任务状态与实际执行不一致。
- 死信队列无 retryCount 上限,允许无限重投或永久静默堆积。
- 事件持久化阻塞调度主路径,或 seq 回退导致审计链断裂。
- revision 冲突被静默忽略而非返回 `REVISION_CONFLICT`,导致并发覆盖不可见。
## 六、完成验收标准
### 功能验收
- [ ] 所有关键状态迁移经过统一状态机。
- [ ] 业务层不再直接依赖 `GatewayStore` 的内部 Map 结构。
- [ ] `create/claim/settle/retry/cancel/approve` 支持幂等或冲突检测。
- [ ] 任务拥有 leaseId、leaseExpiresAt、attemptId 等执行关联字段。
- [ ] 旧 lease 的迟到回报不能覆盖新 attempt。
- [ ] `/healthz`、`/readyz` 和配置检查可用。
- [ ] 错误响应包含稳定错误码和 requestId。
- [ ] 审计能够还原一次任务从创建到终态的完整过程。
- [ ] 任务带 `revision`,并发 settle 只有一个成功,`REVISION_CONFLICT` 正确返回。
- [ ] 事件持久化到 Repository,SSE 断线重连后能补播断线期间的事件。
- [ ] 回调有 HMAC 签名、失败重试 3 次、全部失败进死信队列。
- [ ] 取消信号传播到执行节点,已取消任务的迟到回报被拒绝,子任务级联取消。
- [ ] 死信带 `retryCount`/`originalTaskId`/`deadReason`,超过 `maxDeadRetries` 标记 `exhausted`。
### 回归验收
```powershell
npm test
npm run test:conversation
npm run e2e-team
npm run recovery-demo
npm run stress
```
要求:所有命令退出码为 0;若本机没有真实 LLM key,真实 LLM 命令不得被伪装为成功,使用项目既有 OFFLINE/fixture 方式验证。
### 性能验收
- 在现有压测规模下,任务无重复领取、无永久丢失、无重复终态。
- 新增状态机、Repository、审计后,现有 stress 测试吞吐下降不超过 **10%**。
- 必须提供改造前后的 baseline 对比数据:吞吐(task/s)、P95/P99 排队延迟、P95/P99 执行延迟、重复领取数、丢失任务数。
- 单次状态迁移不产生不必要的全量历史文件重写。
- 事件持久化不能阻塞调度主路径;事件写入失败的降级行为必须有测试覆盖。
### 安全验收
- 错误、审计、状态、导出、日志中均不存在 key/token/password/secret 明文。
- 默认 token、非法路径、非法状态、伪造 lease、越权 settle 均有拒绝结果。
- 不引入新的 shell 拼接、动态代码执行或不受限文件路径。
## 七、交付物格式
每个模型必须提交以下内容:
```text
1. 修改文件清单
2. 实现摘要
3. 状态迁移表
4. Repository 接口说明
5. 幂等和 lease 语义说明
6. 测试命令与实际结果
7. 未完成项和已知风险
8. 与其它模型合并时的冲突点
```
禁止只提交“已完成”“测试通过”等无证据结论。
## 八、四模型协作规则
四个模型使用完全相同的任务书,但必须各自独立工作区或分支,禁止互相覆盖文件。建议每个模型优先形成完整闭环,不按文件机械拆分:
- 模型 1:重点实现状态机(A)+ 任务版本与乐观锁(G)并接入 server/scheduler。
- 模型 2:重点实现 Repository(B)+ 事件持久化(H)并接入 store/application 层。
- 模型 3:重点实现幂等/lease(C)+ 回调加固(I)+ 死信加固(K)+ 错误码和审计关联(D)。
- 模型 4:重点实现取消传播(J)+ 补测试(F)+ 健康检查和配置校验(E)。
上述分工只是减少冲突,**验收标准对四个模型完全相同**。每个模型都必须审阅并修正自己改动触及的兼容性问题,不能以“不是我的分工”为理由留下失败测试。
合并顺序建议:
1. 状态机、错误码和 revision(任务 A、D、G)。
2. Repository 兼容层和事件持久化(任务 B、H)。
3. 幂等和 lease(任务 C)。
4. server、scheduler、node-runtime 接入。
5. 回调加固、死信加固、取消传播(任务 I、K、J)。
6. 健康检查和测试补齐(任务 E、F)。
7. 全量回归与人工审阅。
## 九、给模型的执行指令
你正在参与 `scheduler-gateway-epoch` 第一阶段 P0 生产化基线开发。请严格按照本任务书执行:先阅读 `README.md`、`ARCHITECTURE.md`、`IMPLEMENTATION.md`、`ADR.md` 和相关源码;确认现有行为后再修改。你的目标是提交可合并代码,而不是写方案。保持 Node >=20、零第三方运行时依赖、REAL/OFFLINE 诚实标记和现有 API 兼容。完成后必须运行对应测试,报告实际命令、退出码、修改文件和剩余风险。不得修改归档目录,不得删除用户已有改动,不得泄漏任何密钥。
## 十、阶段完成定义
只有同时满足以下条件,第一阶段才算完成:
- 四个模型的实现可以合并为一个统一版本。
- 全量回归测试通过,新增测试覆盖状态机、Repository、幂等和 lease。
- 所有任务终态和异常路径都有审计证据。
- 进程重启、节点掉线和迟到回报不会造成重复终态。
- 现有面板、插件、看板桥和团队编排功能未被破坏。
- 文档、测试数字和实际代码状态一致。