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

18 KiB
Raw Permalink Blame History

调度网关第一阶段统一开发任务书

任务书版本:1.0
发布日期:2026-09-09
适用项目:scheduler-gateway-epoch 6.0.0
执行方式:四个模型同时执行同一份任务书
阶段名称:P0 生产化基线

一、任务目标

在不破坏现有网关、节点、团队编排、控制台、插件和测试能力的前提下,把当前单机原型的核心任务生命周期改造成可验证的工程化基础:

  1. 任务状态只能通过受控状态机迁移。
  2. 存储访问通过 Repository 接口隔离,现有文件存储行为保持兼容。
  3. claim、settle、retry、cancel、create 等关键操作支持幂等和租约。
  4. 进程、节点和执行器异常后,任务不会永久丢失、重复完成或无限重试。
  5. 所有关键行为有结构化审计和测试证据。

本阶段不做数据库迁移、不做多租户、不做 SSH 真实接入、不重写团队编排器、不开发新模型适配器。

二、当前代码基线

重点代码:

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:实现任务状态机

新增任务状态机模块,例如:

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 抽象

新增存储接口,例如:

src/domain/repositories.js
src/adapters/storage/file-repository.js

要求:

  • 至少抽象任务、节点、事件、审计、死信和产物索引访问。
  • 现有 GatewayStore 可作为兼容实现,但业务服务不得继续依赖 Map 的内部结构。
  • 保留 persist:false 的干净内存测试模式。
  • 保留现有 JSON 落盘和 WAL 能力,不能因抽象而关闭恢复逻辑。
  • Repository 方法必须区分“找不到”“冲突”“非法状态”“存储失败”。
  • 为未来 SQLite/PostgreSQL 实现保留异步接口,即使当前 FileRepository 内部仍是同步实现。

建议最小接口:

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:统一错误、审计和请求关联

要求:

  • 新增稳定错误码集合,例如:
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 错误统一为:
{
  "error": {
    "code": "INVALID_STATE_TRANSITION",
    "message": "任务当前不可取消",
    "requestId": "req_xxx",
    "details": {}
  }
}
  • 保持现有接口状态码兼容;如必须变化,在兼容字段中同时提供新错误结构。

任务 E:健康检查和配置校验

新增:

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。

回归验收

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 拼接、动态代码执行或不受限文件路径。

七、交付物格式

每个模型必须提交以下内容:

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。
  • 所有任务终态和异常路径都有审计证据。
  • 进程重启、节点掉线和迟到回报不会造成重复终态。
  • 现有面板、插件、看板桥和团队编排功能未被破坏。
  • 文档、测试数字和实际代码状态一致。