Files
qinglong/docs/adr/ADR-0119-reviewed-production-cluster-control-route-composition.md
T

81 lines
4.9 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.
# ADR-0119:受审的生产 Cluster Control 路由组合
- 状态:AcceptedCluster Control 生产业务路由组合已实现;Worker execution composition 已由 ADR-0121 实现,独立 ingress 与完整产品部署仍默认关闭)
- 日期:2026-07-23
- 关联 RFCQL-RFC-0001 D-43、D-46、D-48、D-117、D-118
- 关联 ADRADR-0048、ADR-0049、ADR-0051、ADR-0052、ADR-0105、ADR-0118
## 背景
Cluster Control 已分别具备 readiness-first HTTP surface、reviewed route registry、Bearer
认证、Project Policy、耐久安全审计、Run 低敏读取和用户取消 mutation,但生产 application
仍要求部署调用方自行拼装 registry、authorizer 与 admission pipeline。这样会产生两个问题:
1. 已实现的 `run.cancel` 可能没有注册,形成“协议与存储已完成、生产入口不可达”;
2. 调用方可以无意中构造不同的路由集合,使副本之间的权限面和审计 operation 漂移。
该缺口属于组合根责任,不需要新 package、schema、数据库连接或后台 lifecycle。
## 决策
### 1. 生产路由表是代码级精确白名单
`@qinglong/cluster-control/production` 提供唯一受审的生产业务组合。当前白名单固定为:
- `GET /api/v3/projects/{projectId}/runs/{runId}``run.get` / `run.read`
- `POST /api/v3/projects/{projectId}/runs/{runId}/cancellation``run.cancel` / `run.stop`
组合根不接受额外 route definition 或 registry 注入。未知路径继续在认证和读取 body 前返回
`404 route_not_found`。新增生产业务路由必须修改该白名单、补协议/权限/审计测试并更新 RFC,不能
由部署配置动态扩权。
### 2. 复用同一认证、Policy 与审计 authority
组合根只消费 `bootstrapClusterControlRuntime()` 在 PostgreSQL readiness 后交付的受限
`ClusterControlAssemblyInput`
- API credential authenticator
- Project Policy repository
- Run reader 与 cancellation repository
- write-only security audit sink。
路由仍通过既有两阶段 admissionauthentication、Project Policy 与 durable audit 全部在 body
读取和 repository mutation 前完成。取消 Event ID 默认由进程内 CSPRNG 生成,测试可注入工厂;
调用方不能提交 Event ID 或系统 cancellation reason。
### 3. 生产 wrapper 不复制资源 ownership
`startProductionClusterControlApplication()` 复用既有 application/HTTP/bootstrap 顺序。它只接受
`EnabledClusterControlConfig`,并从同一 config 原子创建 runtime Pool 与 one-way availability fence
调用方不能分别传入两个可能错配的 authority。PostgreSQL、startup recovery、Scheduler、取消
convergence、admission drain 和 listener shutdown 继续由原组合根拥有;新增 production stack 只拥有
路由表和 admission pipeline,其 reconcile/start/stop 是无资源边界。disabled/profile gate 由 config
loader 在调用 production 入口前完成;低层 application 的 disabled 路径仍不绑定端口或打开 PostgreSQL。
本决策不打开 Worker ingress,不装配 Worker headless lifecycle,也不绕过“缺少共享 Artifact store
则 Remote completion 保持关闭”的门禁。
### 4. 设备与集群边界
本实现留在既有 `cluster-control` package,未新增 workspace importer、timer、socket、连接、队列或
schema。Edge/Standalone 发布闭包不导入该 subpathCluster Control 副本共享同一静态白名单,可按
副本水平扩展而不复制权限定义。该切片完成时 workspace 为 23 个 importer、292 个 TypeScript
source file;后续包粒度收敛与 availability 实现后当前为 21 个 importer、307 个 TypeScript source
fileproduction Pool/fence 绑定仍留在既有 cluster-control package。
## 被否决的替代方案
1. **继续让部署脚本手工拼 registry**:无法保证副本间路由与权限一致,也无法证明取消入口已开放。
2. **从环境变量或插件动态加载生产路由**:把安全边界降级为部署文本,绕过 reviewed registry。
3. **在 route factory 内自行打开 PostgreSQL**:复制 Pool ownership,并破坏 readiness/recovery 顺序。
4. **把 Worker ingress 合并到同一 listener**:混合 API credential 与 mTLS/`ql3w` authority,扩大故障域。
5. **为两个路由新建 package**:没有独立部署或权限责任,只增加依赖和发布成本。
## 验收证据
1. production subpath 编译并导出固定 `run.get|run.cancel` operation 白名单。
2. 读取和取消均经过认证、Project Policy 与 durable audit;取消使用服务端 Event ID。
3. 未审查路径在认证/body/repository 前 fail closed。
4. config loader 的 disabled gate 不调用 production wrapperproduction wrapper 对 disabled config 在取得任何数据库或 listener 前同步拒绝。
5. cluster-control 目标测试、全 workspace build、cluster/edge dependency audit 均通过;当前 package 数为 21。