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,112 @@
# ADR-0047: PostgreSQL Project Policy 与 RoleBinding Fence
- 状态:Proposed
- 日期:2026-07-19
- 关联 RFCQL-RFC-0001 D-27 至 D-32、D-34 至 D-44、D-45
## 上下文
ADR-0046 已强制 `/api/v3` 在读取 body 前完成 Authentication、Policy 与安全审计,但当时 cluster-control 只有 Policy port,没有 PostgreSQL 权威实现。若直接注入 allow-all stub、复用进程内 role Map,或 deep-import legacy Sequelize adapter,都会让 readiness 已通过的多副本控制面在授权事实上一致性失效。
既有 SQLite `0017-project-policy` 已定义 ownerless `default` Project、append-only versioned RoleBinding、六类 subject、固定 role matrix、mutation replay 与 expected-version CAS。PostgreSQL 必须保持相同领域结果,但不能逐字复制 SQLite 较弱的数据库约束或单写者事务假设。
## 决策
### 1. Profile-neutral Policy contract 进入 runtime-core
`@qinglong/runtime-core/project-policy` 冻结:
- `user / api_app / mcp_client / agent / system / worker` 六类 subject
- Project status、owner/admin/operator/viewer role 与 permission vocabulary
- Project、RoleBinding、snapshot 与 append command exact-shape normalizer
- `ProjectPolicyRepository` port
- role matrix、archived read-only、Agent `require_approval` 和 Project/Binding version fence。
legacy 与 runtime-core 由 parity test 比较 vocabulary、effect、reason 和 fence。过渡期可以存在实现副本,但不得产生两套 Policy 语义。
### 2. `pg-0004-project-policy` 推进 capability v3
reviewed migration 新增:
- ownerless `ql3.projects`,只创建无隐式 owner 的 `default` baseline
- `ql3.project_role_bindings`,复合主键包含 Project/subject/version
- Project 内 mutation 唯一索引、current lookup 与 subject lookup 索引;
- active 必须携带有效 rolerevoked 必须为 `role IS NULL`
- Project timestamp、subject、mutation 和版本使用数据库 CHECK
- capability 从精确 v2 predecessor 推进为:
`{"project_policy":1,"run_core":1,"run_retry_policy":1}`
Drizzle metadata、schema contract、migration checksum 和真实 catalog 必须 lockstep。readiness 看到 capability/history/table/index/CHECK/FK 任一漂移都保持 not-ready。
### 3. Runtime role 只能 append RoleBinding
精确 runtime privilege 为:
- `projects`: SELECT、INSERT、UPDATE,禁止 DELETE
- `project_role_bindings`: SELECT、INSERT,禁止 UPDATE、DELETE
- schema CREATE 继续禁止。
Role revoke 通过 append 新 version 表达,禁止覆盖或删除旧授权事实。migration role 仍是唯一 DDL authority。
### 4. PostgreSQL append 使用数据库 fence
`PostgresProjectPolicyRepository.append()` 固定:
1. `SERIALIZABLE` transaction 与硬 statement/lock/idle timeout
2. `SELECT Project ... FOR UPDATE`,同时证明 Project 存在并串行化该 Project 的 Policy mutation
3. 先按 `(project_id, mutation_id)` 检查精确 replay;不同 payload 使用同 mutation ID 时冲突;
4. 读取 subject 最新 version,并与 `expectedCurrentVersion` 精确比较;
5. 只 INSERT version N+1serialization/deadlock/lock conflict 有界重试;
6. 行畸形、重复 identity、未知 SQL 错误与连接失败统一映射为低敏 unavailable。
两个并发 expected-version writer 只能有一个 winner。winner 的精确 replay 返回原事实,loser 不得通过最后写覆盖改变 role。
### 5. Readiness 后同时装配 Run 与 Policy Repository
cluster-control 只有在 capability v3、catalog 和 runtime role 全部通过后,才向 application factory 提供同一 Pool 上的 `RunRepository``ProjectPolicyRepository`。admission 使用共享 `ProjectPolicyEngine` adapter 产生 allow/deny/approval 与 fence;无 Project scope 的 operation 默认 deny。
## 被否决的替代方案
### 复用 legacy Sequelize Policy adapter
拒绝。该 adapter 明确 SQLite-only,并会让 cluster artifact 反向加载 legacy root、Sequelize 与本地数据库假设。
### 用 upsert 保存 subject 当前 role
拒绝。会丢失撤权历史,使旧 approval/fence 无法解释,也无法区分精确 replay 与覆盖写。
### 只靠唯一索引竞争,不锁 Project
拒绝。mutation identity 与 subject version 是两个约束域,仅处理 `23505` 不能证明 Project 存在检查、replay 和 version winner 在同一快照内成立。
### 缓存 role 并异步失效
当前拒绝。未定义可靠 version invalidation 前,缓存会让 revoke 后的旧 allow 在其他 pod 继续生效。首版先走有界数据库点查。
## 影响
### 正向
- cluster-control 不再需要 allow-all Policy seam
- SQLite/PostgreSQL 对相同 snapshot 返回相同 effect/reason/fence
- RoleBinding 为 append-only durable fact,支持审计、撤权和并发裁决;
- runtime role 即使被业务 bug 驱动,也不能 UPDATE/DELETE binding history
- Project row lock 将同一 Project 的低频权限 mutation 与高频 Policy read 分离,不阻塞普通 read。
### 代价
- 同一 Project 的 RoleBinding mutation 被串行化;这是权限管理低频路径的有意选择;
- runtime role 对 Project 需要 UPDATE 权限以执行 `FOR UPDATE` 和后续 Project 管理,必须继续由 Repository/Policy 限制;
- 当前 ADR 只完成 Project/RoleBinding core;后续 ADR-0048/0049 已补齐受审 route registry、cluster credential authentication 与持久化安全审计,Project/Role/credential 管理 API、audit retention/query 和 Approval PostgreSQL tables 仍未完成;
- `runs.project_id``projects.id` 的跨历史 backfill/FK 尚未决策,不能在已有 alpha 数据上盲加约束。
## 验证
1. runtime-core normalizer/engine unit test覆盖 exact shape、role matrix、archived、Agent approval 和 fail-closed。
2. legacy/runtime parity test覆盖 vocabulary、effect、reason 与 fence。
3. migration checksum、capability predecessor、Drizzle/schema/catalog lockstep test全部通过。
4. fake Pool test覆盖 resolve、SERIALIZABLE 顺序、exact replay、version/mutation conflict、rollback/retry 和 corrupt row。
5. 真实 PostgreSQL 双连接测试证明同 expected version 只有一个 winnerwinner 可精确 replay。
6. 真实数据库直接 INSERT 证明 revoked+role 与逆序 timestamp 被 CHECK 拒绝。
7. 独立 runtime role 证明 Project 无 DELETE、RoleBinding 无 UPDATE/DELETE、schema 无 CREATE。
8. PostgreSQL 16/18 × x64/arm64 CI 继续作为正式 server/readiness 证据;较旧 PostgreSQL 必须被 production readiness 拒绝。