9.3 KiB
ADR-0117:流式 Remote Worker Artifact 与原子 Completion
- 状态:Accepted(S3-compatible 共享对象存储已由 ADR-0120 实现,生产执行面装配已由 ADR-0121 实现;retention 与完整 Worker 产品生命周期仍默认关闭)
- 日期:2026-07-23
- 关联 RFC:QL-RFC-0001 D-14、D-17、D-24、D-26、D-68、D-85、D-108、D-111、D-114、D-115、D-116
- 关联 ADR:ADR-0024、ADR-0058、ADR-0109、ADR-0112、ADR-0115、ADR-0116
背景
ADR-0116 固定了 Worker 的 upload-before-completion 顺序,但中央侧仍只有抽象 port。若通过普通 JSON/base64 发送 64 MiB 日志,Worker 和 controller 都会产生大对象复制;低配路由设备无法承受, 高并发节点也会放大 GC。若 controller 先把日志写入本地盘,多副本 completion 可能看不到上传 结果;若只信任 Worker 上报的 digest,终态可引用不存在或已漂移的内容。
completion 还必须关闭两个崩溃窗口:running ACK 已完成后的正常终结,以及进程已经完成、但
Worker 在 running ACK 前崩溃后从 durable starting 证据恢复。网络 upload 不得跨 PostgreSQL
事务持锁,终态又必须一次完成 Lease、Attempt、Run 和 Event,不能由多个 API 逐步拼接。
决策
1. 不新增 package,使用两个 exact versioned contract
wire contract 放入 @qinglong/runtime-core/remote-worker-completion;Worker adapter 放入现有
@qinglong/worker-runtime/completion-transport;Cluster service 与 PostgreSQL repository 分别
留在现有 cluster-control、cluster-postgres。它们与既有部署、依赖、权限和版本责任相同,不满足
D-85 的拆包条件。
Artifact schema 为 qinglong/remote-worker-artifact-upload@v1。request 使用 4-byte big-endian
JSON header length,header 最多 4 KiB,随后是最多 64 MiB 原始内容。header 只携带 execution
fence、Artifact identity、内容长度和 truncation;Worker/Session identity 由 path 绑定,不在 body
重复。media type 固定为 application/vnd.qinglong.worker-artifact,必须 identity encoding 和精确
Content-Length,不允许 chunked、压缩或 multipart 产生第二套解析语义。
completion schema 为 qinglong/remote-worker-completion@v1,request/response 分别最多 16/4 KiB,
字段 exact,未知字段拒绝。普通 Worker ingress JSON 的 4 KiB 默认与 64 KiB hard cap 不因 Artifact
stream 放宽;只有 Artifact route 使用独立 64 MiB + 4 KiB + 4 bytes hard cap。
2. 复用单一 mTLS client 和有界 backpressure
Worker Artifact uploader 与 completion client 复用 ADR-0112 的 WorkerIngressHttpsClient、TLS 1.3
mTLS credential provider、canonical ql3w authorization 和单 keep-alive Agent。stream writer 按
已知长度逐 chunk 写入,Node socket 返回 backpressure 时等待 drain;短读、超读、错误类型、
错误 route、响应越界或 authority 漂移全部 fail closed。
Artifact preamble、TLS credential material、JSON request/response 临时 buffer 在消费后尽力清零。 完成协调器继续只从 durable inbox 装配 lease capability;通用 Artifact source/store 不接收 Worker credential,也不记录 capability。
3. 认证和授权先于内容,store 必须跨副本共享
Cluster HTTP surface 必须先完成 route、mTLS/credential、Worker/Session 和 admission prepare,再读取 stream body。Artifact service 读取有界 header 后,在共享 Attempt advisory fence 下通过 PostgreSQL repository 精确复验 Worker generation、offer、Lease generation/token digest/version/expiry、Run、 Attempt 和状态;授权成功后才把原始内容交给 store。
ClusterRemoteWorkerArtifactStore 是 immutable put-if-absent port:key 至少绑定 Project、Run、
Attempt、log Artifact ID,返回实际消费的 length、SHA-256 和 truncation。实现必须跨所有
cluster-control replica 可见,并对相同 identity + digest 提供 exact replay;同 identity 不同内容
拒绝。controller 本地目录、Pod ephemeral volume 或仅 process-local Map 只能用于测试,不能声明为
生产 cluster store。
store 不接收 Worker/Session identity、raw lease token 或 callback capability。上传事务不跨 store
I/O 持有数据库锁;因此 completion 必须重新调用 inspect,精确匹配 immutable Artifact 的 ID、
length、SHA-256、truncation 和 Project/Run/Attempt authority 后才能进入终态事务。
4. completion 是一个 PostgreSQL 权威事务
repository 使用既有 Attempt advisory fence,并按 Worker Session、Run/Attempt、Run Lease 的固定
顺序 FOR UPDATE,随后读取一次数据库时间。它复验 path principal、Session/generation、offer、
Lease generation、raw token 的 SHA-256、expected version、callback sequence/digest 和 Artifact
receipt。raw token 只在应用内比较摘要,不进入 SQL、Event 或错误。
允许的 live 起点只有:
starting + dispatching:覆盖 spawn 后/running ACK 前的 authenticated receipt 恢复;running + running:正常 completion。
Worker finished time 不能晚于数据库时间加五分钟。最终状态首先服从 durable cancellation/timeout intent,再解释 exit code;不得让迟到成功覆盖取消或 timeout。单一 transaction 必须:完成 Lease 并把 version 加一、终结 Attempt、终结 Run、写 Attempt 与 Run 两个 Event。Event ID 由服务端生成, terminal timestamp 取数据库 observation、Worker evidence 和既有 start/create facts 的安全上界。
5. exact replay 由终态和 Event 共同认证
只有 Lease 已 completed、Lease/Attempt version 精确为 request expected version + 1、Run/Attempt
终态和 callback/Artifact/exit/error facts 完全一致,并且已有 Attempt completion Event 具有 exact key
set 与 exact payload,才返回 already_completed。Event 的 from_status 只允许 starting|running。
任何字段漂移为 replay_mismatch;其他已终态 aggregate 返回 already_terminal,不能被当前 Worker
改写,也不能重复追加 Event。
6. 生产默认关闭条件
ADR-0120 已选择一个 S3-compatible 共享 immutable adapter;任何 production composition 都必须显式 注入它,缺失时 Artifact/completion routes 不注册或 admission fail closed。不得用 controller-local fallback 自动降级。ADR-0121 已提供默认关闭的 Worker execution composition,但 transport 类存在仍 不得自动启动 timer、Pull 或 completion loop;完整 Session/credential 产品生命周期继续由外层门禁。
本 ADR 不完成 Artifact range read/retention、spool deletion、对象存储 credential/临时前缀 lifecycle 的产品配置,也不声明多 Pod/failover 实机支持。durable timeout/cancellation/lease-loss stop 已由 ADR-0118 完成;S3 SSE/KMS adapter 与单节点 MinIO 真实验证由 ADR-0120 完成;默认关闭的生产执行面 装配与 drain-before-release 由 ADR-0121 完成。
被否决的替代方案
- JSON/base64 Artifact:峰值内存和复制与日志大小成正比,拒绝。
- multipart/form-data:引入第二套复杂边界解析且没有当前单 Artifact 请求的收益,拒绝。
- controller 本地文件作为 cluster store:多副本不可见、Pod 重建丢失,拒绝。
- 先写 store、后信任 Worker 自报 digest:不能证明中央内容与终态一致,拒绝。
- 跨 upload 网络调用持 PostgreSQL transaction:长锁和连接占用放大 store 尾延迟,拒绝。
- completion 分别更新 Lease、Attempt、Run:中途崩溃产生不可解释部分终态,拒绝。
- 只按终态行做幂等:无法证明历史 completion payload,拒绝。
- 为 upload/client/repository 各建 package:没有独立依赖/权限/发布责任,违反 D-85,拒绝。
验收证据
- runtime contract 拒绝未知字段、越界 header/content、错误 authority 和结果不一致。
- HTTP surface 在读取 stream 前完成 admission,拒绝错误 media type、encoding、Content-Length 和 未完整消费;普通 JSON cap 保持不变。
- Worker client 使用 fixed length、backpressure 和共享 Agent;短读、超读、route confusion 拒绝。
- 真实 TLS 1.3 mTLS 测试完成分帧 Artifact upload 与 completion,并验证 path/body identity 分离。
- Artifact service 先授权再 store,验证实际消费 length/digest,completion 前重新 inspect。
- PostgreSQL repository 从
starting与running收敛,在单 transaction 完成 Lease/Attempt/Run/ 双 Event,cancellation/timeout 优先。 - exact replay 同时验证终态、version 和 Event payload;非法
from_status、digest 漂移拒绝。 - raw lease token 不进入 SQL/Event;Event identity 由服务端生成。
- Worker、runtime-core、cluster-control、cluster-postgres 完整测试通过,未新增 workspace package、 migration、schema、timer、queue 或常驻连接。
- ADR-0125 的本机 arm64 PostgreSQL 18.4 physical-promotion 门在 runtime-role completion
transaction 的 driver-confirmed
COMMIT后终止 backend,使首次调用得到 unavailable;同一 fence/receipt/Event identity 重放为already_completed。standby 在 promotion 前、promoted primary 在 timeline 2 上均保持 succeeded Run/Attempt、completed Lease version 5、2 条 Event、 2 个 dedupe key 和 0 duplicate;故障范围是 PostgresClient 边界,不是 raw-wire packet-loss。