Files
qinglong/docs/adr/ADR-0137-postgresql-plugin-package-install-repository-and-project-lock-authority.md

150 lines
7.8 KiB
Markdown
Raw Permalink 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-0137PostgreSQL Plugin Package 安装仓库与 Project Lock Authority
- 状态:AcceptedPostgreSQL 三表、完整 lock、capability v17、admin-only repository、
SECURITY DEFINER Project lock、跨 adapter 合同与物理 HA 门已完成;通用 activation
coordinator、标准 OCI stage verifier、Kubernetes ConfigMap publisher、一次性 admin
recovery process、exact-registry credential provider、独立镜像与最小权限
Job/RBAC/真实 Kubernetes 专项门已可组合;Approved Action 产品 consumer
与管理入口仍未开放)
- 日期:2026-07-24
- 关联 RFCQL-RFC-0001 D-08、D-09、D-127、D-132、D-134、D-135
- 关联 ADRADR-0125、ADR-0129、ADR-0134、ADR-0135、ADR-0136
## 背景
ADR-0136 已让 edge/standalone 通过单 SQLite authority 持久化 Plugin Package
installation 历史、当前 head 与 mutation replay,但 cluster 节点仍没有同一
runtime-core contract 的 PostgreSQL adapter。若集群另造状态机,SQLite 与
PostgreSQL 会在 first-create、terminal replacement、旧 mutation replay 和恢复扫描
上逐渐产生不同安全语义。
实现过程中还出现一个 PostgreSQL 特有边界:创建 installation 前必须确认 Project
为 active,并把该事实锁到 transaction 结束。普通 `SELECT` 不能阻止并发归档;
`SELECT ... FOR SHARE` 又要求调用角色拥有目标表 UPDATE privilege。仅为了取得行锁就
授予 `ql3_admin` 通用 Project UPDATE,会扩大数据库权限而不是表达所需能力。
## 决策
### 1. Adapter 留在既有 `@qinglong/cluster-postgres`
Repository 只通过
`@qinglong/cluster-postgres/plugin-package-install` 显式 subpath 导出,不从 root、
runtime、admin 或 worker-ingress 入口导出,不新增 workspace package和第三方依赖。
它只接受 admin Pool;默认 cluster-control、worker 与 migration composition 都不
加载该 subpath。实现存在不代表产品已暴露安装入口。
### 2. `pg-0018` 冻结三类耐久事实
`pg-0018-plugin-package-installs` 创建:
- `plugin_package_installs`:每个 installation 的不可变 `lock_json`、当前 record 和全
generation 历史;
- `plugin_package_install_heads`:每个 Project/Package 的唯一当前 authority
- `plugin_package_install_mutations`mutation ID/digest 与 resulting record digest。
CHECK、FK、partial recovery index、Project history index 与 mutation result index 同
typed Drizzle schema、SQL migration、migration manifest 和 readiness contract
lockstep。Repository `findLock` 每次重新规范化并重算 lock digest。PostgreSQL stream
由 exact `pg-0017` 前驱推进到 18 条 migration、
`control-core` capability v17、30 张受审表,并发布
`"plugin_package_install":1`
runtime 与 worker-ingress 对三表没有任何 table privilege。`ql3_admin` 对 install/head
只有 SELECT/INSERT/UPDATE,对 mutation ledger 只有 SELECT/INSERT,所有角色均无
DELETE。
### 3. 用单用途 SECURITY DEFINER 表达 Project 锁能力
migration 创建
`ql3.lock_active_plugin_package_project(varchar) -> boolean`。函数:
1. owner 固定为 `ql3_migration`
2. 使用 `SECURITY DEFINER` 和固定 `search_path=pg_catalog, ql3`
3.`FOR SHARE` 读取目标 Project,锁住 concurrent status update/delete
4. 只返回 active 与否,不返回 Project 数据;
5. 撤销 PUBLIC 默认 EXECUTE,只授予 `ql3_admin`
6. runtime、worker-ingress 均不得执行,admin 也不因此获得 Project UPDATE。
readiness 对函数 name/signature、owner、security-definer、volatility、search path、
PUBLIC ACL 及当前角色 EXECUTE 权限进行 exact 审计。未知 ql3 function、owner 漂移、
PUBLIC execute 或角色越权都使 activation 失败关闭。
### 4. Repository 复用完全相同的领域合同
create/commit 使用短 `SERIALIZABLE` transaction,依次验证:
- active Project lock
- exact current head 或空 head
- runtime-core 重新计算的 create/commit command
- installation version、record digest、mutation ID/digest CAS
- history、head 与 mutation ledger 原子提交。
只对 PostgreSQL `40001``40P01``55P03` 做最多三次 transaction retry。
连接错误、COMMIT response loss 和其他 outcome-unknown 不透明重放,必须由调用方读取
durable mutation/head 事实后裁决。exact replay 返回当前已前进 record,不倒写旧状态。
恢复只扫描当前 head 中 `queued | staged | activating`,使用稳定
Package/installation cursor,每页最多 64 条;旧 generation 不能重新取得 authority。
### 5. 两个 adapter 共享一份可执行语义合同
根级 `pluginPackageInstallRepositoryContract.cjs` 同时驱动 SQLite 与真实 PostgreSQL
覆盖 create/find、exact replay、mutation drift、CAS/stale state、旧 mutation replay、
terminal head replacement 和 current-head recovery pagination。adapter-specific
测试只保留 catalog、corruption、role/entrypoint 与数据库故障行为。
### 6. 产品安装入口继续失败关闭
后续 ADR 已补齐标准 OCI source/stage verifier、一次性 recovery process、独立
cluster-admin image 与 namespaced ConfigMap-only Job/RBAC;这些恢复能力不等于面向
用户开放安装入口。当前仍不实现:
- 具体 Approved Action 产品 consumer 与管理入口;
- Task/Workflow/Prompt/Tool/Trigger 的原子 generation publisher
- operator repair、旧 generation GC
- publisher revoke/index、管理 API/CLI/UI 或自动更新;
- Runtime Extension 或动态代码加载。
因此 production cluster 可以在已存在耐久 installation 的前提下运行受限恢复门,但仍
不能通过产品入口创建安装、激活新的资源 generation 或启用动态代码。
## 拒绝的方案
- 新增 `plugin-package-postgres` workspace package:拒绝;单一 adapter 文件和迁移属于
现有 cluster-postgres,拆包只会增加 importer/build/发布成本。
- 直接授予 admin Project UPDATE 以便行锁:拒绝;技术性锁需求不应变成通用数据修改
authority。
- 普通 SELECT 后写 installation:拒绝;Project 可在检查后并发归档。
- 在 repository 内复制 SQLite 状态机:拒绝;领域语义必须由 runtime-core 和共享合同
单点定义。
- 将 repository 加入 admin/root 默认导出:拒绝;会让短生命周期安装 authority
在无产品闭环时变成易达能力。
## 影响
- 集群节点获得与本机完全对等的 crash-safe installation/head/mutation authority。
- 低配 edge/standalone 制品不安装或加载 cluster-postgres,本切片不改变其常驻资源。
- workspace importer 仍为 21 个,没有新增 package 或依赖。
- PostgreSQL readiness 多审计一个单用途函数,但常驻进程不增加 timer、watcher、
sidecar 或数据库连接。
## 验证
- migration checksum、SQL/Drizzle/schema/function contract、三角色 readiness 与显式
subpath 隔离:21/21
- local-sqlite 全量及共享合同:68/68
- cluster-postgres 非数据库全量:124 pass、1 个条件 skip、0 fail
- PostgreSQL 18 单角色真实集成:26 pass、3 个角色条件 skip、0 fail
- PostgreSQL 18 四角色真实集成:28 pass、1 个同角色 backend termination 条件
skip、0 fail
- `pnpm test:postgres-ha:ql3`21 个 HA 子门和总 `passed` 全为 true2026-07-25
最近一次本机 arm64 证据为 fail-closed 312.866 ms、fresh activation
364.602 ms、旧主 `pg_rewind` 1,744.481 ms0 unexpected domain side effect。
21 个 importer 的 clean build 与全量 package 聚合测试均退出 0dependency/source
boundary、edge import 和六种 Profile artifact 门禁均通过且 `findings=[]`。六种制品
顺序复验的最大值为 2,849,582 bytes/439 files/78 loaded modules,最大 RSS delta
13,221,888 bytes,低于 4 MiB/512 files/16 MiB 硬门禁。production composition 与
真实 operator 安装恢复仍是后续独立 Gate。