mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
102 lines
5.2 KiB
Markdown
102 lines
5.2 KiB
Markdown
# ADR-0161:Tool Execution Start 与 Artifact 的不可变绑定
|
||
|
||
- 状态:Accepted
|
||
- 日期:2026-07-26
|
||
- 关联:ADR-0158、ADR-0159、ADR-0160;RFC D-123/D-149/D-150
|
||
|
||
## 背景
|
||
|
||
Tool invocation input 与 redacted preview 已成为独立不可变 Artifact,plan、
|
||
admission 和 start barrier 只携带 reference。若数据库只保存 start barrier JSON,
|
||
数据库无法证明该 start 实际引用哪一对已持久化 Artifact;历史缺行、镜像列漂移或
|
||
相同 ID 的错误内容可能在重启、重放或 PostgreSQL promotion 后被误接受。
|
||
|
||
同时,直接扩展既有 start-barrier 表会要求 SQLite 重建热表,并把 Artifact identity
|
||
复制到所有 start 查询路径。低配设备需要保持单次短事务和零后台进程,集群则需要让
|
||
复合身份随 PostgreSQL WAL 原生复制并维持最小权限。
|
||
|
||
## 决策
|
||
|
||
### 1. 使用独立的一对一绑定表
|
||
|
||
- SQLite:`ToolExecutionStartArtifactBindings`;
|
||
- PostgreSQL:`ql3.tool_execution_start_artifact_bindings`。
|
||
|
||
每个 `start_id` 只能有一行。该行保存 Project、action、input/preview Artifact ID 与
|
||
Artifact digest,以及 input/action/preview/redaction digest。`bound_at_ms` 必须与
|
||
barrier 的 `started_at_ms` 一致。
|
||
|
||
input 与 preview 父表增加受审复合唯一索引。绑定表通过两个复合外键精确引用完整
|
||
Artifact 镜像身份,并通过第三个外键引用 start barrier。Artifact digest 已覆盖
|
||
key ID、algorithm、大小等完整 envelope,因此绑定表不再复制这些可由 digest 证明的
|
||
字段。
|
||
|
||
### 2. 同一事务先写 barrier,再写绑定
|
||
|
||
SQLite 继续复用共享 `BEGIN IMMEDIATE` authority;PostgreSQL 继续复用现有短
|
||
`SERIALIZABLE` start transaction。Trace、Audit、StepRun/Run CAS、RunEvent、
|
||
mutation、barrier 和 Artifact binding 任一失败,整个 start 都必须回滚。
|
||
|
||
Artifact pair 必须在 start 前已经持久化。缺少 Artifact、Project/action 不一致或
|
||
任一 digest 漂移均由复合外键失败关闭,不能在 start 事务里补造 Artifact。
|
||
|
||
### 3. 读取时以关系事实反证 JSON
|
||
|
||
Repository 查询使用 `LEFT JOIN` 读取绑定镜像,并把每个字段与规范化 barrier
|
||
reference 对比。使用 `LEFT JOIN` 是刻意的:升级前已存在但没有绑定行的 barrier
|
||
仍能被发现,随后明确返回 unavailable;若使用 inner join,它会被误判为“不存在”,
|
||
重试写入后才以唯一键冲突结束,无法区分历史不完整事实。
|
||
|
||
不对旧 barrier 进行推测性 backfill。缺少绑定就是不可恢复的执行证据缺口,必须由
|
||
后续 inspect/manual recovery 处理。
|
||
|
||
### 4. Capability、readiness 与权限锁步
|
||
|
||
- SQLite:`0059-tool-execution-artifact-bindings` 建表,
|
||
`0060-capability-v30` 发布 `tool_execution_artifact_binding:1`;当前 60 条
|
||
migration、capability v30、52 张 typed owned table;
|
||
- PostgreSQL:`pg-0032-tool-execution-artifact-bindings` 建表、安装 ACL 并发布
|
||
capability v31;当前 32 条 migration、capability v31、51 张表。
|
||
|
||
PostgreSQL runtime 对绑定表只有 `SELECT, INSERT`;`UPDATE, DELETE` 必须由数据库
|
||
拒绝。admin、Package manager、Package executor 和 Worker ingress 均无表权限。
|
||
双方言 migration manifest/checksum、Drizzle schema、readiness table/index/CHECK/FK
|
||
contract 必须同时前进。
|
||
|
||
## 低配与集群影响
|
||
|
||
- Edge/Standalone 每次 Tool start 只增加一个 12 列有界关系行,无新 package、
|
||
进程、连接、timer、watcher 或缓存;
|
||
- 查询只增加一次主键 `LEFT JOIN`,不会扫描 Artifact JSON 或解密 input;
|
||
- Cluster 绑定行与三组外键随 PostgreSQL WAL 复制,不实现应用层复制协议;
|
||
- start 热路径不解析明文,Artifact key material 仍不进入数据库。
|
||
|
||
## 被否决方案
|
||
|
||
1. **把完整 Artifact JSON 写进 barrier**:复制 ciphertext、扩大热行和恢复读取面。
|
||
2. **只把 Artifact ID 写进 barrier JSON**:数据库无法证明 ID 对应的 digest、
|
||
Project 和 action。
|
||
3. **迁移时从 barrier JSON 回填绑定**:把未经关系外键证明的历史 JSON 升格为可信
|
||
数据库事实。
|
||
4. **用 inner join 隐藏缺失绑定**:把损坏历史伪装成不存在,降低故障可诊断性。
|
||
5. **给 runtime UPDATE 权限**:不可变绑定无需更新,扩大 authority 没有正确性收益。
|
||
|
||
## 验证
|
||
|
||
- runtime-core 全量 313/313;
|
||
- SQLite 全量 116/116,其中 migration/schema/readiness/start repository 定向
|
||
18/18;
|
||
- PostgreSQL package 175 pass/1 条件 skip,其中
|
||
migration/schema/readiness/start repository 定向 35/35;
|
||
- Cluster Control 最小权限与应用组合测试 139 pass、2 条条件 skip;
|
||
- PostgreSQL 真实集成覆盖 Artifact 先持久化、start 与 binding 原子提交、exact replay
|
||
和 durable row 计数;
|
||
- PostgreSQL 18 物理 HA 门覆盖 capability v31/51 表、runtime append-only、四个隔离
|
||
角色拒绝、promotion/readiness 与旧主 rejoin。
|
||
|
||
## 后续门禁
|
||
|
||
1. 遗失 key、损坏 Artifact/绑定行的 inspect/manual recovery;
|
||
2. 首个 trusted built-in adapter 在 durable start 后解封并重新通过 Registry;
|
||
3. adapter response-loss、进程崩溃与副作用幂等恢复证据。
|