Files
qinglong/docs/adr/ADR-0122-single-cadence-worker-session-product-lifecycle.md
T

11 KiB
Raw Blame History

ADR-0122:单 Cadence Worker Session 产品生命周期

  • 状态:Proposedexact wire、共享 client、Session coordinator、capacity oracle、product/process composition、credential material provider 与单身份部署基线已实现;远端 credential recovery、真实 Cluster 全链、Linux/固定设备证据仍是发布 Gate)
  • 日期:2026-07-23
  • 关联 RFCQL-RFC-0001 D-05、D-06、D-14、D-85、D-108、D-120、D-121
  • 关联 ADRADR-0012、ADR-0057ADR-0061、ADR-0108、ADR-0110、ADR-0112、ADR-0121、ADR-0123、ADR-0234

背景

本 ADR 提出时,Cluster Control 已在独立 mTLS/ql3w ingress 上提供 Worker Session 的 register|heartbeat|transition 路由,PostgreSQL repository 也已拥有 generation/version/lease fencing,但 route 仍直接解析手写 JSONWorker Runtime 也没有 Session client。当前增量已用共享 runtime contract 和 Worker product composition 收敛这两处缺口;后续 Gate 仍不得回退到第二套协议。

back/runtime/application/workerHeartbeatLifecycle.ts 只用于早期单控制面孵化:它依赖 legacy Worker domain,自建 heartbeat timer,并由另一个 HeadlessWorkerRuntime 拼接 execution drainer。 ADR-0121 已建立单 journal owner、单 mTLS Agent 和单 Profile cadence 的 production execution plane。 直接复用旧 lifecycle 会重新产生两套 Session shape、两个 timer、两个 client ownership,并可能在 Session offline 或 Agent close 后仍留下需要 completion/lease supervision 的本地进程。

决策

1. Session wire 必须 exact、versioned 且 path-bound

在现有 @qinglong/runtime-core 增加一个懒加载 Session transport subpath,不新增 package。三个 request/response schema 固定为:

  • qinglong/worker-session-register@v1
  • qinglong/worker-session-heartbeat@v1
  • qinglong/worker-session-transition@v1

Worker/Session identity 只来自既有 pathbody 不重复 principal,也不携带 credential。register request 最多 20 KiB,以容纳 16 KiB canonical capability snapshotheartbeat/transition request 与所有 response 最多 4 KiB。未知字段、未知 schema、非 canonical capability、generation/version/status/lease 漂移均 fail closed。register 的 replacement fact 只在 response 中表达,HTTP status 保持统一且可重放的 200。

Cluster ingress 必须调用 contract parser/serializer,不再维护第二套 ad-hoc key list。现有认证和耐久 audit 仍先于 bodyPostgreSQL repository 仍是 generation、version 和数据库时钟的唯一 authority。

2. Session 与 execution 必须共享一个 HTTPS client/Agent

Session adapter 留在 @qinglong/worker-runtime,并消费 ADR-0121 的同一个 WorkerIngressHttpsClient。不得为 register/heartbeat/transition 创建第二个 Agent、第二套 credential provider 或独立 socket budget。client path allowlist 扩展必须精确到三个 Session operation,不放宽 Artifact stream 或通用 URL。

credential provider 仍在每个请求前惰性加载 certificate、private key、trust 与 ql3w token;本 ADR 不允许把这些材料复制进 Session journal。ADR-0123 已实现 active certificate + 原子 token 文件的具体 读取/轮换消费与 Buffer disposeSession transport 已把 401/403/409 分为 credential rejected/session fenced 使 Pull 立即暂停并允许同一 Session 在 token 修复后 heartbeat 恢复。远端 credential issue/revoke/recovery、 delivery acknowledgement 和管理面失败编排仍是后续 Gate,缺失时 product bootstrap 保持默认关闭。

3. Session coordinator 不拥有 timer

新增 caller-driven Session coordinator,只拥有以下显式操作:

  1. register():生成新 UUIDv7 Session,提交 canonical capability 与初始 capacity
  2. tick(now):仅在到达 heartbeat due time 时提交一次 heartbeat,禁止重叠;
  3. beginDrain():持久化 draining + availableSlots=0
  4. disconnect():只在 execution plane 已证明 settled 后持久化 offline

它不得调用 setTimeout/setInterval。ADR-0121 的唯一 Profile cadence 同时驱动 Session due check 和 execution tickEdge/Node 可使用不同 cadence,但 heartbeat 频率独立受 5 秒–5 分钟硬边界约束。临时 transport failure 只能在现有 lease 内由后续 cadence 重试;fence、credential rejection 或本地已观察 lease expiry 必须立即停止 Pull,不能自动用新 Session 接管旧 Session 下仍活动的 execution。

4. Capacity 必须来自单一 execution oracle

部署不得注入与 journal 无关的任意 available-slots 数字。production composition 必须从同一 owner journal、当前 in-flight Pull reservation 与最大并发预算派生 availableSlots,范围始终为 0..maxConcurrentRuns。startup reconciliation 未完成、recovery required、draining 或 Session unavailable 时必须为 0。

能力快照使用 runtime-core 的 canonical Remote Worker capabilitiescapability JSON/hash、Profile、 架构、Runtime 和 Executor 不能由 HTTP response 临时改写。Edge 默认并发与 page 更小,Node 可提高 有界预算;两者都不增加 per-Run timer 或 socket。

5. 启停顺序由一个 product composition root 拥有

启动顺序固定为:

  1. disabled/Profile/config gate,且 disabled 在读取路径和 credential 前返回;
  2. 创建单 credential provider 与单 HTTPS client
  3. 取得 journal owner并完成全部有界 startup reconciliation
  4. 只有无 recovery fact 才注册 Session,并发布由 execution oracle 计算的 capacity
  5. 启动唯一 unref Profile cadence,之后才允许 Pull。

关闭顺序固定为:

  1. execution beginDrain() 先 Abort Pull,但保留 journal owner与 shared Agent
  2. Session beginDrain() 在控制面耐久写入 0 capacity;
  3. 唯一 cadence 继续 heartbeat 与 completion-first supervision
  4. 所有 inbox record settled 后才 transition offline
  5. 最后释放 journal owner、关闭 transport 与 shared Agent。

drain timeout、Session fence、heartbeat lease expiry、offline 失败或 recovery fact 都不得强制 close。 进程仍有可能产生 completion 时,不能为了满足 shutdown 时限丢弃唯一 owner。等待 shutdown proof 的 timer 必须保持 ref;只有常驻 cadence unref。

被否决的替代方案

  1. 直接复用 back/** heartbeat lifecycle:引入 legacy domain、第二 timer 和第二 shutdown owner。
  2. Session 与 execution 各建一个 HTTPS client:重复证书、Agent、socket 与 credential rotation 状态。
  3. 每 500 ms 都发 heartbeatNode cadence 会把副本和路由设备的空闲成本放大。
  4. 先 register available 再做 startup recovery:控制面会把未证明安全的 Worker 当成可调度候选。
  5. drain timeout 后 offline/释放 owner:把预算耗尽伪装成副作用已经停止。
  6. 允许部署直接上报 availableSlots:容量与本地 durable execution authority 可漂移。
  7. 为 Session lifecycle 新建 workspace package:没有独立部署、依赖或权限责任,违反 D-85。

验收 Gate

  1. runtime exact contract 覆盖三个 schema、byte cap、unknown field、authority drift 与 canonical capability。
  2. Cluster ingress 继续证明 auth/audit-before-body,并以 PostgreSQL 16 最小权限角色验证完整 fence/replay。
  3. Worker 真实 TLS 1.3 测试在同一 Agent 连续执行 register、heartbeat、Offer、completion 与 transition。
  4. startup recovery 前零 registerrecovery required 时零 Session/零 Pull。
  5. 单 cadence、heartbeat due coalescing、in-flight 不重叠和本地 lease expiry fail closed 有确定性测试。
  6. shutdown 证明 drain capacity 0 → execution settled → offline → owner/Agent release;所有失败路径保留 authority。
  7. Edge/Node 固定设备资源证据证明没有新增 package、数据库、per-Run timer 或第二常驻 socket。

当前实现证据

  1. @qinglong/runtime-core/worker-session-transport 已提供三个 exact v1 schema、20 KiB/4 KiB request cap、4 KiB response cap、canonical capability 与 authority drift 校验;Cluster ingress 已复用 parser/serializer,并继续在 body 前完成认证与耐久 audit。
  2. @qinglong/worker-runtime/session-transport/session-lifecycle 已在同一 WorkerIngressHttpsClient 上实现 register/heartbeat/transitioncoordinator 无 timer、请求串行、按 due heartbeat,并在本地 lease expiry 后停止暴露可执行 Session。
  3. WorkerExecutionCapacityOracle 只读取 owner journal 与 durable pending claimreconciling、draining、 recovery 和 offline 一律发布 0,不接受部署直接注入 slots。
  4. @qinglong/worker-runtime/product 在 startup reconciliation 后注册 Session,复用 execution 的唯一 cadence;真实 TLS 1.3 测试证明 register → draining → offline 共用一个 socket/Agent,且历史未结算 journal 在零 register 时失败关闭。
  5. 确定性顺序测试证明 Session tick 与 execution tick 不重叠,shutdown 按 execution drain → Session drain → heartbeat/supervision → offline → owner/transport release 收敛,并覆盖 offline 后 owner release 首次 失败的幂等重试。当前 Worker 129/129D-218 本切片未改动共享 contract 或 Cluster ingress,后者继续由 D-216/D-217 的完整回归与 PostgreSQL HA 门覆盖。
  6. ADR-0234 已增加 ql3-worker production binary、disabled-before-secret process config、 direct-file identity bootstrap、持久 certificate store、信号先行和 incomplete-drain 持续持权; 独立 Worker 镜像闭包为 3 个 workspace package/24 个 runtime externalKubernetes 固定 单身份单副本、Recreate、PVC 和 projected authority 私有物化。产品入口仍必须由明确 Worker Profile 配置激活,不能由其他部署默认开启。
  7. macOS arm64/Node 24.18 真实进程+mTLS资源门测得 edge active/peak 67,616,768/71,090,176 bytes、node 67,911,680/71,286,784 bytes,均为一个 TLS 1.3 socket并完成 register/drain/offline64 MiB 被明确否决,edge 暂按至少 96 MiB 规划。尚未完成 Gate:真实 Cluster ingress/PostgreSQL 全链、 credential issue/revoke/recovery、Linux image/cgroup 与固定设备断网/休眠/断电证据。 因此本 ADR 与 D-121 仍保持 Proposed。
  8. 历史 23 package dependency/source audit 为 findings=[]Edge import audit 为 compatible=true;六种本机 制品均通过 pack/offline install/import,最大 standalone-application 为 2,474,124 bytes、411 files、 72 loaded modules,且制品闭包不包含 @qinglong/worker-runtime。runtime-core 新 contract 使本机制品比 D-120 快照增加两个文件,作为“不新增细碎 package”的显式可见成本保留。