Files
qinglong/docs/adr/ADR-0214-owner-fenced-bounded-local-role-binding-query.md
T

147 lines
6.5 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-0214Owner 围栏化、有界的 Local RoleBinding 查询
- 状态:Accepted
- 日期:2026-07-29
- 关联 RFCQL-RFC-0001 D-05、D-27、D-37、D-65、D-72、D-73、D-175、
D-198、D-200、D-203、D-204
- 关联 ADRADR-0028、ADR-0185、ADR-0208、ADR-0210、ADR-0213
## 背景
ADR-0208 的 `policy.role-binding.put|revoke` 要求 target 的 current version,但产品
入口不能读取当前 RoleBinding。operator 仍需直接查询 SQLite,既绕过认证和审计,也
容易把 append-only 历史行误当成当前授权。
RoleBinding 查询与 ADR-0213 的 Project 拓扑查询不是同一种 authority:
- Project create/archive/restore/inspect/list 是实例级拓扑管理,只属于实例
authority Project Owner
- RoleBinding 是 Project-scoped 权限管理,每个 Project 的当前 Owner 应能自治查询
自己 Project,而不能因此枚举其他 Project。
## 决策
### 1. 扩展既有 `ql3-policy`,不新增 package、migration 或常驻资源
在现有 project-policy-administration 四层 subpath 中新增:
- `policy.role-binding.inspect`
- `policy.role-binding.list`
查询只读取 v37 已有的 append-only RoleBinding 表并写已有 security audit 表。
workspace 保持 22 个 package,不新增依赖、daemon、timer、watcher、listener、缓存
或端口。
### 2. 每个 Project 的当前强认证 Owner 只能查询自己的 Project
local-admin 对 request `projectId` 固定请求 Owner-only `project.manage`。SQLite 在
同一个 `BEGIN IMMEDIATE` 事务内复用 put/revoke 的最终 authority helper,重新验证:
1. authenticated credential、Identity、有效期和 pepper provenance
2. 目标 Project active/version
3. actor 最新 active Owner RoleBinding/version。
验证后才读取目标或列表并原子写 allowed audit。admin/operator/viewer 和未绑定 User
均不能查询。secondary Project Owner 可以查询其自己的 Project,但不能把自身
`project.manage` 用于另一个 Project。
这条路径不要求 ADR-0211 的实例 authority anchor;错误地加入该要求会破坏
Project-scoped 自治。
### 3. inspect 只返回 target 的最新 revision
`policy.role-binding.inspect` 接受 Project、target、request ID 和单一 audit event
ID。SQLite 按 version 降序只取一条:
- 从未存在时返回 `found:false`
- 当前 active 时返回 role
- 当前 revoked 时不返回 role
- 历史 active revision 不得覆盖最新 revoked revision。
成功输出只包含 Project、target、version、state、可选 role 和 created timestamp。
不返回 mutation ID、changedBy、credential、pepper、数据库路径或内部 audit。
### 4. list 只列出每个 subject 的最新 revision
`policy.role-binding.list` 要求:
- `limit` 为 164
- `state``active|revoked|all`
- `role``owner|admin|operator|viewer|all`
- 可选 cursor 精确包含 `{subjectType, subjectId}`
SQLite 先以相关子查询选择每个 `(projectId, subjectType, subjectId)` 的 max version
再应用 state/role filter,按 `(subjectType ASC, subjectId ASC)` 查询 `limit + 1`
只有确有下一条时才返回最后一条可见 binding 的 cursor。`role != all` 自然排除
role 为 null 的 revoked binding。
禁止返回历史 revision、offset、任意排序、模糊搜索、客户端 SQL 片段或无界数组。
每一页是独立授权、独立审计的 current-head snapshot,不是跨 command 的长事务。
翻页期间发生 put/revoke 时,要求严格同一时点清单的 operator 必须从第一页重查。
### 5. 查询与审计使用同一个最终事务
query command 不携带 mutation ID、expected version 或 failure-audit ID,只携带单一
audit event ID。成功查询与 allowed audit 原子提交;credential/Project/Owner fence
在服务授权后漂移时事务回滚,CLI 以同一 event ID 写低敏 denial audit。
## 不采用方案
### 直接暴露 `ProjectPolicyRepository.resolve`
拒绝。它只解析一个 subject 的 Policy snapshot,不提供 actor Owner authority、
credential fence 或同事务 audit,也会把内部 repository 变成产品 transport。
### 返回全部 append-only 历史
拒绝。产品 mutation 需要 current version,历史列表容易误判撤权状态且输出无界。
历史审计应由未来专门的 bounded audit/query contract 提供。
### 只允许实例 authority Project Owner 查询所有 RoleBinding
拒绝。这会破坏 Project-scoped RBAC 自治,并迫使 secondary Project 的日常成员管理
依赖实例根 Owner。
### 另建 RoleBinding query package 或管理 daemon
拒绝。查询与 mutation 共用 Project authority、SQLite、CLI 和交付闭包;拆包或常驻
服务增加供应链碎片和路由设备空闲成本。
## 影响
正向影响:
- put/revoke 的 expected version 不再要求直接 SQL
- append-only 历史被明确折叠为每个 subject 的 current head
- Project Owner 自治与实例拓扑 authority 保持分离;
- 64 条硬上限约束 CPU、内存和输出;
- put/revoke/query 共用同一个最终 Owner fence helper
- SQLite contract 保持 v37workspace 保持 22 包。
代价与限制:
- 每次查询写一条 security audit,并短暂取得 SQLite write reservation
- 当前不提供历史 revision、RoleBinding approval 或 break-glass 恢复;
- Cluster 继续使用独立 PostgreSQL/RBAC management transport。
## 验证
- GitNexusSQLite append method 1 个直接上游、CLI request normalizer 3 个上游,
repository/class/service factory 均为 LOW,未命中已索引执行流程;
- 真实 SQLite/Owner CLI 18/18
- inspect 只返回 revoked current v3,不回退历史 active
- missing 返回 `found:false`
- 两页 keyset 覆盖四个最新 subject,无重复或静态遗漏;
- active viewer filter 只返回当前 viewer
- secondary Project Owner 可查询自己的两个 Owner
- secondary Owner 查询 foreign Project 被拒绝并写 denial audit
- Project admin 查询被 `permission_missing` 拒绝;
- limit 65 在打开数据库前拒绝;
- credential 在服务授权后漂移时,最终 inspect 事务拒绝且不提交 allowed audit
- Project lifecycle/query、RoleBinding mutation、防锁死和容量回归;
- local-admin 与 local-owner-cli strict TypeScript 通过;
- runtime-core/local-sqlite 完整 TypeScript 仍只受锁定依赖未物化影响,本切片未出现
类型错误;
- 不新增 migration、package、生产依赖或 deployment contract 版本。