# Worker 通信与执行契约 v1 已实现服务端执行事务、WebSocket 接入、[独立 Go 底层协议 SDK](../sdk/worker.md)、[自动 Worker 运行器](../sdk/worker-runtime.md) 与 [尝试/证据查询](execution-history.md),通过 [P2 阶段验收](../reports/phase-2.md),包含真实 WSS、协议 Schema、自动续租、ACK 丢失/断网恢复及进程强杀后的节点接管。底层 WorkerConnection 不自动管理处理器;NewWorker / Run 提供自动管理。 ## 1. 接入与身份 入口 `GET /v1/worker/connect`,子协议必须为 `pulse.worker.v1`。Authorization 使用 Bearer 凭据;禁止 URL 查询参数。握手先校验 workers:connect,消费控制操作再次校验 workers:consume、队列范围和凭据有效性。 初始化开发凭据可显式增加 Worker 权限: ```sh go run ./cmd/orbit-admin bootstrap \ --application worker-demo --namespace default --environment development \ --queue objects --worker-access --output .local/worker.credentials.json ``` 默认 bootstrap 仍只有 P1 的五项权限,现有凭据不会自动扩权。生产细粒度凭据管理在 P6 完成。 当前 orbit 命令监听 HTTP,开发可使用 ws;生产需要受控的 TLS 终止。WSS 回归使用真实 TLS 测试服务器及受信任测试证书,没有跳过证书校验。传输库锁定 github.com/coder/websocket v1.8.15,使用上下文、读写期限、子协议和默认 Origin 校验,关闭压缩。[上游版本记录](https://github.com/coder/websocket/releases/tag/v1.8.15)、[上游接口文档](https://pkg.go.dev/github.com/coder/websocket@v1.8.15)。 ## 2. 帧格式 只接受 JSON 文本帧;单帧最多 512 KiB,业务结果最多 256 KiB。重复键、未知字段、无效 Unicode 和非法数值按 HTTP 相同规则拒绝。 机器可读契约及各方向定义见 [JSON Schema 说明](worker-schema.md)。控制字段必须显式提供;epoch/sequence 等计数使用无前导零的规范十进制字符串。请求禁止响应字段,空字符串/null 也不例外。 ```json { "protocol_version":"1", "type":"task.start", "message_id":"44444444444444444444444444444444", "session_id":"33333333333333333333333333333333", "body":{ "task_id":"11111111111111111111111111111111", "attempt_id":"22222222222222222222222222222222", "epoch":"1", "lease_secret":"<执行秘密,仅通过受控连接传输>" } } ``` 所有 ID 为 32 位小写十六进制;epoch、sequence、revision 用十进制字符串。message_id 标识一次操作,重连重报保持不变;session_id 必须换为当前连接身份。服务端响应以 reply_to 关联请求: ```json { "protocol_version":"1", "type":"error", "message_id":"55555555555555555555555555555555", "reply_to":"44444444444444444444444444444444", "session_id":"33333333333333333333333333333333", "error":{"code":"LEASE_LOST","message":"attempt no longer grants execution authority"} } ``` 普通成功响应 type=ack,body 为下表对应结果。服务端主动派发 type=task.assign,无 reply_to。错误仅包含稳定分类和公开消息,不包含 SQL、业务参数或秘密。 ## 3. 注册、恢复与容量 连接后 5 秒内必须发送 worker.hello,hello 不携带 session_id。 | hello 字段 | 含义 | | --- | --- | | instance_id | Worker 进程实例 ID;进程重启必须换新值 | | instance_secret | 32 字节随机秘密的无填充 base64url 编码,共 43 字符;同进程重连保留 | | queue_id | 当前进程绑定队列 | | capabilities | 1–32 个 handler / handler_version / payload_version 精确版本组合,不可重复 | | capacity | 总执行槽数量,1–256,不是可用槽的累加值 | | cancellation_notifications | 可选 bool,默认 false;true 接收 task.cancel 通知,自动 Worker 默认开启 | 重连必须保持凭据、队列、处理器集合和进程秘密,处理器集合的排列顺序不影响身份。新连接递增会话代次并使旧会话失效。知道 instance_id 或 task_id 不能接管执行。 worker.welcome 返回 session_id、epoch、server_time、expires_at、lease_duration_ms、draining。当前窗口为会话 45 秒、执行租约最多 30 秒;注册及能力声明不等于开始授权。 **派发在收到第一次 worker.capacity 确认后才启用**,便于客户端先恢复旧尝试。 | 类型 | body | 返回 | | --- | --- | --- | | worker.resume | AttemptRef,字段见上方 task.start 示例 | 当前 LeaseStatus;成功迁移有效尝试到新会话,不创建尝试或延长期限 | | worker.capacity | sequence 字符串、capacity 0–256、drain bool | 更新后的 welcome 字段 | | task.assign | 服务端主动发送 | task 完整任务、attempt_id、epoch、lease_secret、lease_until、server_time | capacity 使用单调递增序号和覆盖值。同序号同内容可重复,同序号不同内容或旧序号冲突;capacity=0 不释放已有任务的并发许可。drain=true 是进程级持久化排空,重连保留;后续必须保持 drain=true,尝试清除返回 STATE_CONFLICT。新进程使用新实例身份。 一个 Worker 进程当前绑定一个队列。可以运行多个 Worker 进程或后续 SDK 实例消费不同队列;不能通过重连静默切换队列。 ## 4. 开始、续租与结果 | 类型 | body | 语义 | | --- | --- | --- | | task.start | AttemptRef | 领取后再次检查当前会话、尝试、租约、队列暂停及流程门控,保存 RUNNING 后确认 | | task.renew | AttemptRef | 只续期当前 RUNNING;新消息可续租,重复原消息只取回原回执 | | task.finish | ref: AttemptRef、outcome、result、error_code、可选 retryable | 保存明确结果或未知效果;返回操作回执和当前授权 | outcome 为 SUCCEEDED / FAILED / UNKNOWN / CANCELLED。result 为非 null JSON 或证据引用。非成功结果必须带 1–64 字符的稳定 error_code;SUCCEEDED 不带错误分类。retryable 省略为 false,只有 FAILED 可设 true。CANCELLED 要求已保存取消意图且确认本次停止,不能用普通 context 错误推断;协议与升级顺序见 [取消契约](cancellation.md)。FAILED/true 受次数与时限限制进入退避;UNKNOWN 仅在冻结策略为 IDEMPOTENT、未取消且预算允许时自动恢复,其他情况转 NEEDS_REVIEW。历史不确定效果不会被后续失败或取消覆盖;完整规则见 [安全重试](retries.md)。 开始、续租和结束返回 CommandResult: ```json { "receipt":{ "operation":"start", "task_state":"RUNNING", "attempt_state":"RUNNING", "task_revision":"3", "lease_until":"2026-09-17T08:00:30Z", "disposition":"APPLIED" }, "lease":{ "active":true, "state":"RUNNING", "lease_until":"2026-09-17T08:00:30Z", "server_time":"2026-09-17T08:00:00Z" }, "deduplicated":false } ``` receipt 是原操作的不可变确认;lease 是本次重新核验的执行权,两者不能混用: - 只有 lease.active=true、lease.state=RUNNING、没有 cancel_requested_at,且仍在保守的本地有效期内,才允许处理器推进。LEASED 的 active 只表示预占有效。取消后的 active 可为 true,用于确认当前结果;不允许继续业务执行或延长期限。 - 原 start 回执可能仍表示 RUNNING,但当前 lease.active=false;旧 ACK 不能让已过期、已结束或已被替换的尝试复活。 - 有效期不超过租约窗口、会话期限、任务总截止和从 authorized_at 起计算的单次尝试时限。 - 客户端需要扣除请求总耗时及安全余量,用本地单调时钟停止执行,不能假定本地墙上时钟与数据库一致。 - 同结果重报不重复释放许可或产生成功事件;即使换 message_id,也返回该结果第一次已应用的回执,不借用后续尝试的状态。同消息改变内容或 retryable 返回 IDEMPOTENCY_CONFLICT。 - 迟到、未开始或与已保存结果冲突的内容,经过身份和秘密验证后保存为 EVIDENCE_ONLY;不改变任务权威结果,不当作新的执行授权。 - 断线后原结果仍保留稳定消息 ID,可在新会话恢复/核对后重报。不会因重连创建第二次业务执行。 ## 5. 领取、并发与回收 平台在同一事务中分配队列并发许可、创建尝试、递增任务执行代次和次数、保存 LEASED 与派发事件。领取本身消耗一次 max_attempts 预算,包括最终未获开始授权的尝试,预算不会因重连或过期恢复重置。 运行中的进程总容量跨其所有旧/新会话统计。声明容量减少、断开连接或重复结果,都不能重复释放额度。当前已实施队列 max_concurrency;rate_per_second、其他配额和调度策略在 P3 完成。 每个节点每 2 秒发起一批恢复扫描,每批最多 100 项: - 未批准开始即到期:有剩余次数和截止时间时按冻结策略退避;否则无历史不确定效果为 FAILED,有未核实效果为 NEEDS_REVIEW。 - 已批准开始后失联:尝试 UNKNOWN;RECONCILE_ONLY 转 NEEDS_REVIEW,IDEMPOTENT 仅在次数及时限允许时退避重试,并保留 effect_uncertain。 - WAITING 超过总截止或最晚开始时间:无历史不确定效果为 FAILED,否则 NEEDS_REVIEW。 - 回收再次锁定队列、任务、尝试并读取数据库时间,与续租/完成串行化;多个节点不能重复释放许可或重复终结事件。 安全重试已使用同一决策逻辑处理有效结果与租约回收;下一执行时间持久化在任务中,重启不丢失等待。当前不提供人工重试或核查解除接口。 ## 6. 连接保护与当前边界 当前节点上限 128 条 Worker 连接;每连接入站队列 4 帧、出站队列 8 帧,派发保留 ACK 缓冲空间,每秒最多发起 4 次新派发尝试。读空闲期限 60 秒、写期限 5 秒、单个数据库控制操作期限 8 秒。慢消费者导致连接关闭时,已提交状态仍由稳定身份恢复。 这些是有界的开发基线,不代表经过容量压测的生产参数。当前同队列操作使用较粗的行锁协调,吞吐与公平性需要 P6 按负载验证。服务退出会显式关闭被 HTTP Upgrade 接管的连接;断开不会直接撤销外部已发出的请求。 协议 Schema 和真实进程强杀恢复已通过回归,见 [P2 阶段验收](../reports/phase-2.md)。[运行中取消](cancellation.md) 已实现并通过回归;凭据轮换、数据库故障切换、跨主机演练与容量验收属于后续生产验证。自动运行器对非协作处理器提供有界退出与状态报告,不能强制停止其外部副作用。