feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
@@ -0,0 +1,242 @@
# ADR-0222:按 generation 绑定的 Plugin Package 自动化发布账本
- 状态:Proposed
- 日期:2026-07-29
- 关联 RFCQL-RFC-0001 D-144、D-145、D-147、D-175、D-207、D-211
- 关联 ADRADR-0150、ADR-0151、ADR-0153、ADR-0221
## 背景
ADR-0150 已把 Package 中的 Workflow 与 Prompt 解析成同一份 immutable
materialized revision,但“已经物化”不等于“已经发布给运行时”。如果运行时直接读取
materialized revision,会出现以下问题:
- active generation 切换时,Workflow 与 Prompt 可能分别可见;
- disable/enable 只撤出 Task 与 Tool,自动化定义仍可被新执行消费;
- 集群多副本无法证明读取的是同一 generation、同一 publication head
- 路由设备若用 watcher、目录扫描或常驻缓存弥补,会增加空闲内存、I/O 和恢复状态。
Secret binding 具有独立授权、密文材料和审计边界,不能与只包含低敏定义的
Workflow/Prompt publication 合并。
## 决策
### 1. Workflow 与 Prompt 使用同一 publication
`qinglong/plugin-package-automation-publication@v1` 一次发布同一 Package generation
中的完整 Workflow 与 Prompt 定义:
- target 精确绑定 Project、Package、installation、lock、generation、
generation digest 与 materialized revision digest
- definitions 分别按 ID 排序并拒绝重复;
- Workflow 与 Prompt 各最多 128 项;
- canonical publication JSON 最大 12 MiB
- publication 使用 domain-separated SHA-256 digest。
同一 generation 不拆成 Workflow head 与 Prompt head。消费者只能看到完整 generation
或旧完整 generation,不能看到半批定义。
### 2. append-only digest chain
每个 `(Project, Package)` 只有一个 CAS head,历史 publication 永不 update/delete。
- 每个 active Package generation 都必须拥有一条 publication;包含 Workflow/Prompt
时状态为 `active`,不包含时状态为 `absent` 且 definitions 为空;
- `absent` 是 generation tombstone,禁止旧 generation 的自动化 head 跨过一个空
generation 继续可见;
- 首条 publication 固定为 `active|absent`、version 1、无 predecessor
- Package generation 升级从任意 current head 追加更高 generation 的
`active|absent` publication;允许恢复直接跨过已不再 active 的中间 generation
但 generation 必须严格前进;
- lifecycle disable 追加 `active → withdrawn`
- lifecycle enable 追加 `withdrawn → active`
- `absent` 不参与 lifecycle toggle
- lifecycle successor 必须保留同一 target 与同一 definitions,并绑定 lifecycle
event digest
- version、predecessor digest 与 `publishedAtMs` 单调递增。
uninstall 只能从 disabled 进入,因此 automation head 已是 withdrawn。v1 不再追加
“retired” publication,保留 withdrawn immutable history;物理回收由独立 retention
协议处理。
### 3. 双方言 durable repository
SQLite 使用:
- `QingLong3PluginPackageAutomationPublications`
- `QingLong3PluginPackageAutomationPublicationHeads`
- migration `0079-plugin-package-automation-publications`
- capability `local-core@40`
PostgreSQL 使用:
- `ql3.plugin_package_automation_publications`
- `ql3.plugin_package_automation_publication_heads`
- migration `pg-0043-plugin-package-automation-publications`
- publication capability `control-core@42`
- runtime-only start guard migration
`pg-0044-plugin-package-automation-start-guard`
- current capability `control-core@43`
publication 表 append-onlyhead 只允许 CAS 推进。双方言 repository 都支持
`findCurrent``findByDigest``publish` 与有界 keyset `listPendingPage`。pending
source 只返回“active install 已有 materialized revision,但 automation head 缺失或
generation 不匹配”的 Package。materialized revision fence 不匹配、fork、
JSON/index projection 漂移或缺失 predecessor 均失败关闭。
PostgreSQL runtime、manager 与 package executor 可 SELECT;只有 package executor
可 INSERT publication,并对 head INSERT/UPDATE。没有业务角色取得 DELETE。
### 4. 生命周期同事务联动
ADR-0221 lifecycle repository 不调用另一个自带事务的 repository。automation
repository 提供显式 transaction-bound writer,由 lifecycle 已持有的 SQLite
`BEGIN IMMEDIATE` 或 PostgreSQL `SERIALIZABLE` transaction 调用。
对 current automation head 为 `active|withdrawn` 的 Package
- disable 要求 current automation head 精确匹配 active Package generation 且为
active,然后在 lifecycle event/receipt、Task revision 与 Tool snapshot 的同一事务
追加 withdrawn publication
- enable 要求同一 generation 的 current head 为 withdrawn,在同一事务追加 active
publication
- uninstall 要求 current head 已 withdrawn,不创建新的定义版本;
- 第二次 authorization fence 失败、外键失败、head CAS 失败或任一 capability 写失败
时,整笔事务回滚。
`absent` tombstone 没有可撤出的定义,因此 lifecycle transaction 不为其追加空 toggle
消费者仍以 Package lifecycle/quarantine authority 作为外层准入围栏。
publication 的 lifecycle event 外键要求 lifecycle event 先在事务中插入,再写
publication,最后才允许 COMMIT。这个顺序不改变原子可见性。
### 5. Profile 与资源边界
- 不新增 workspace package
- 不新增 daemon、timer、watcher、listener、socket 或常驻缓存;
- Edge/Standalone 复用单 SQLite operation authority
- Cluster 复用 caller-driven package executor 与单连接 transaction
- 禁用 AI 的 Profile 不因该账本引入 AI provider、SDK 或 credential
- Secret material、SecretRef、Prompt 渲染输入与模型输出均不得进入 publication。
### 6. caller-driven publication 与恢复顺序
initial/next publication 已由 profile-neutral coordinator 发布,并在双方言启动链路中
固定为:
`install recovery → Task materialize/reconcile → Workflow/Prompt publication → Tool snapshot → admission`
- coordinator 在发布前后各观察一次 active generation;换代返回 `superseded`,不把
旧 generation 冒充 current
- 同 generation exact replay 返回 existing,并保留 lifecycle 已写入的
`withdrawn` 状态;
- conflict/invalid 进入 `manual_required`availability 失败进入 `retry`
- 每页和每轮硬上限均为 64Local Edge/Standalone 无显式配置时使用 8×8;
- 不创建 resident reconcilerLocal 复用一个 SQLite operation authorityCluster
复用 caller-driven package-executor Job。
### 7. 安全事实优先的启动门
publication head 只表达 generation 与正常 lifecycle,不把 quarantine 或 publisher
revocation 重写成虚假的 `withdrawn`。启动必须通过独立、精确的
`PluginPackageAutomationPublicationStartGuard`
- 输入固定为 Project、Package 与 current publication digest;旧 digest、非 active
publication、非 current active install、non-active lifecycle 一律返回 false
- SQLite 在同一 operation authority 内同时检查 current publication/install、
lifecycle 与精确 installation/lock quarantine event
- PostgreSQL 只向 `ql3_runtime` 暴露
`SECURITY DEFINER plugin_package_automation_start_allowed`runtime 不取得
quarantine、provenance 或 revocation 表的直接读取权;
- PostgreSQL guard 与 publisher revocation 使用同一 transaction-level signer
advisory lock。若启动观察先完成,它发生在撤销提交之前;若撤销先取得锁,启动等待
后必定观察到 receipt 并返回 false
- pending-source 与 publication writer 同样排除 security-fenced generation,避免
recovery 在隔离或撤销后重新发布能力;
- 不新增 safety-state 镜像表、daemon、timer、watcher 或常驻 denylist。quarantine
event 与 publisher revocation receipt 始终是事实源。
调用方必须在“记录一次自动化启动”的同一数据库事务内消费 PostgreSQL guard;把一次
boolean 查询缓存后再异步启动仍属于 TOCTOU,不能视为生产执行门已经关闭。
### 8. 尚未关闭的产品门
本 ADR 已关闭 durable publication、lifecycle overlay、双方言 quarantine 启动收敛,
以及双方言 publisher revocation 的即时启动收敛:Local 由文件型不可变撤销
receipt 桥接到 SQLite 精确 installation/lock quarantineCluster 由 PostgreSQL
receipt 直接参与启动判定。但不把定义解释为可执行能力。D-211 继续 Proposed
直到至少完成:
1. Workflow Run/StepRun executor 与 admission fence
2. Prompt 产品入口、参数绑定、Policy/Audit 与内容治理;
3. Secret binding consumer。
在这些门关闭前,消费者必须同时检查 Package lifecycle/quarantine authority,不能只因
automation head 为 active 就执行。
## 不采用方案
### 直接读取 materialized revision
拒绝。materialization 是 immutable source fact,不是运行时 publication head。
### Workflow 与 Prompt 分别发布
拒绝。会引入同 generation 部分可见和双 head 恢复协议。
### 把 Secret binding 放入 publication
拒绝。低敏定义发布不能取得 Secret 授权和密文材料 authority。
### 生命周期提交后异步撤出 automation
拒绝。崩溃窗口会留下 Task/Tool 已撤出但 Workflow/Prompt 仍 active。
### 新建 automation workspace package 或 watcher
拒绝。现有 runtime-core 与双方言 storage subpath 足够,额外部署边界没有独立价值,
并会增加路由设备供应链和空闲资源。文件数量不是 package 边界;本能力没有独立部署、
权限域、重依赖或发布责任,因此即使代码增长也继续使用现有 package 的显式 subpath。
## 当前验证
1. runtime-core publication/coordinator 定向 9/9,通过 canonical digest、跨
generation successor、`absent` tombstone、lifecycle withdraw/restore、并发换代
与有界 recovery 分类;
2. SQLite repository 定向 8/8,覆盖 exact current start guard、quarantine 后
pending-source 收敛、publication 拒绝、跨 generation `absent` tombstone 与损坏
失败关闭;
3. SQLite lifecycle 定向测试证明第二次 authorization 检查失败时,Task、Tool
snapshot、lifecycle facts 与 automation head 全部回滚;
4. Local application startup 固定执行 Task→automation→Tool,定向启动回归 14/14
SQLite runtime 只在调用时惰性加载 publication repository
5. PostgreSQL 18.4 arm64 HA fixture 中,同一 Package publication 从 v1 经四次
disable/enable 原子推进到 v5,包含 1 Workflow 与 1 Prompt5 条 publication 和
active head 经 `remote_apply`、timeline 1→2 promotion、旧主 fencing、
`pg_rewind` 及 fresh control replicas 后保持一致;
6. PostgreSQL `pg-0044` 把 control-core 推进至 v43start function 只授予 runtime
package executor/admin/manager/worker ingress 均无执行权;迁移、checksum、
schema/readiness 与 repository 定向 43/43
7. HA gates
`pluginPackageAutomationPublicationTransitionsAtomically=true`
`pluginPackageAutomationPublicationSurvivesPromotion=true`
`pluginPackageAutomationRecoverySourceConverges=true`
`pluginPackagePublisherRevocationImmediatelyFencesAutomation=true`
`pluginPackageAutomationSecurityFenceSurvivesPromotion=true`
`pluginPackageAutomationStartGuardIsRuntimeOnly=true` 且总
`gates.passed=true`;撤销前 current publication 允许,revocation receipt 提交后、
quarantine 后和 promotion 后均拒绝;
8. 调度器 COMMIT-response-loss 同轮修复为“发送 COMMIT 后绝不透明重试”,回归 7/7,
HA 证明只产生 1 Run、1 Attempt、2 Event、0 duplicate occurrence。
9. Cluster caller-driven recovery 已把 automation publication 放在 Task publication
与 Tool snapshot 之间;Cluster PostgreSQL 全量 211 pass/1 条无 DSN 条件 skip
最新 HA 总
`gates.passed=true`,且未访问 registry、未拉取镜像。
10. Local 签名 catalog 已覆盖 active install → materialized Task/Workflow/Prompt →
active automation 的完整链路;撤销 publisher key 后,不可变 receipt callback
以精确 installation/lock provenance 写入 SQLite `withdrawn` quarantineraw
automation publication 仍保持原 generation/lifecycle 事实,但 exact start guard
立即返回 false。测试在 quarantine 与 snapshot 已 durable、文件型 trust current
generation 尚未 promotion 的边界注入崩溃,观察到 current generation 3、
pending generation 4、`recoveryRequired=true` 且启动已拒绝;重放同一命令收敛为
generation 4,随后为 `existing`,SQLite 始终只有 1 条精确 quarantine event。