6.6 KiB
ADR-0213:Owner 围栏化、有界的 Local Project 查询
- 状态:Accepted
- 日期:2026-07-29
- 关联 RFC:QL-RFC-0001 D-05、D-27、D-37、D-65、D-72、D-73、D-175、 D-200、D-201、D-202、D-203
- 关联 ADR:ADR-0028、ADR-0185、ADR-0208、ADR-0210、ADR-0211、 ADR-0212
背景
ADR-0212 提供了 policy.project.create|archive|restore,但部署者仍无法通过受支持
产品入口取得 Project 当前版本或发现已有 Project。archive/restore 的 CAS 因而会迫使
operator 直接查询 SQLite;直接 SQL 又绕过强认证、实例 authority、Owner fence 和
security audit。
Project 数量虽然在 Edge/Standalone 分别被限制为 16/128,产品查询仍不能依赖该容量 事实返回无界数组。查询还必须允许实例 operator 看见 archived Project,否则无法取得 其恢复版本,但“可见”不能使 archived Project 重新获得业务权限。
决策
1. 继续扩展既有 ql3-policy,不新增 package 或 migration
在现有 project-policy-administration 四层 subpath 中新增:
policy.project.inspect;policy.project.list。
查询只读取 v37 已有的 QingLong3Projects current head,并写已有 security audit 表,
因此 SQLite contract 保持 v37。workspace 保持 22 个 package,不新增第三方依赖、
daemon、timer、watcher、listener、缓存或端口。
2. 查询只属于实例 authority Project 的当前强认证 Owner
command 携带 authorityProjectId。local-admin 要求强认证 User 在该 Project 上通过
Owner-only project.manage。SQLite 在同一个 BEGIN IMMEDIATE 事务内再次复验:
- authenticated credential、Identity、有效期和 pepper provenance;
- ADR-0211 实例 authority anchor;
- authority Project active/version;
- actor 最新 active Owner RoleBinding/version。
复验成功后才读取 Project,并在同一事务写 allowed audit。secondary Project Owner 即使拥有自身 Project,也不能枚举实例 Project 拓扑。授权失败不读取目标对象。
3. inspect 返回精确 current head,不存在是低敏结果
policy.project.inspect 只接受目标 projectId、request ID 和单一 audit event ID。
成功结果只包含:
found;- Project ID、name、slug、status、version;
- created/updated timestamp。
不返回 RoleBinding、credential、Secret、数据库路径或内部 mutation ledger。
已授权但不存在返回 found:false;未授权调用不会以 found/not-found 形成存在性 oracle。
4. list 使用硬上限和稳定 keyset
policy.project.list 要求:
limit为 1–64;status为active|archived|all;- 可选 cursor 精确包含
{slug, projectId}。
SQLite 按 (slug ASC, projectId ASC) 查询 limit + 1,最多返回 limit 条,并仅在确有
下一条时返回最后一条可见记录组成的 cursor。不支持 offset、任意排序、模糊搜索或
客户端 SQL 片段。slug 当前唯一且尚无 rename 产品操作,复合 cursor 仍保留确定性的
总排序。
每一页是一次独立授权、独立审计的 current-head snapshot,不是跨 command 的数据库 长事务。若翻页期间发生 create/archive/restore,operator 要求严格的同一时点清单时 必须从第一页重新执行;cursor 不得被描述成跨页 snapshot token。
archived Project 可被 inspect/list,但既有 Project Policy 仍对其业务操作默认拒绝; 查询不修改状态、不恢复 RoleBinding,也不释放容量。
5. 每次查询都是独立、可审计的短生命周期 ceremony
query command 使用单一 audit event ID,不携带 mutation ID、expected version 或
failure-audit ID。成功查询与 allowed audit 原子提交;credential/authority/Owner fence
在授权后漂移时,事务回滚,CLI 以同一 event ID 写低敏 denial audit。command file
仍必须是 deployment root 内当前 UID 所有的 0600 私有文件。
不采用方案
让 operator 直接查询 SQLite
拒绝。它绕过 authentication、authority、fence 和 audit,并把表结构变成产品 API。
因为 Edge 最多 16 个 Project 就返回无界数组
拒绝。Standalone 有 128 个,未来 Profile 也可能变化;无界接口会把存储容量策略泄漏 为内存和 wire 契约。
只提供 inspect,不提供 list
拒绝。部署者仍需预先知道 Project ID,无法发现遗留或 archived Project。
使用 offset pagination
拒绝。并发 create 会导致重复/遗漏,且大 offset 的成本随表增长。稳定 keyset 更适合 低配设备和未来 PostgreSQL 对齐。
新建 Project query package 或管理 daemon
拒绝。查询与 lifecycle 共用 authority、数据库、CLI 和交付闭包;拆包或常驻进程会 违反 ADR-0185 并增加路由设备空闲成本。
影响
正向影响:
- archive/restore CAS 不再要求直接 SQL;
- archived Project 可发现、可审计,但仍不具备业务权限;
- list 的 CPU、内存和输出大小由 64 条硬上限约束;
- secondary Project Owner 不能枚举实例拓扑;
- SQLite contract 保持 v37,workspace 保持 22 包。
代价与限制:
- 每次查询写一条 security audit,并短暂取得 SQLite write reservation;
- 当前只支持 status 过滤和 slug/ID 正序,不支持搜索、rename 或 authority transfer;
- Cluster 管理面仍使用独立 PostgreSQL/RBAC transport,不能复用本机 command file。
验证
- GitNexus:runtime repository interface 2 个上游、SQLite administration 类 1 个直接 消费者、Owner CLI runner 3 个上游、共享 authority transaction helper 1 个直接调用, 均为 LOW,未命中已索引执行流程;
- 真实 SQLite/Owner CLI 13/13:
- archived Project inspect 与 missing
found:false; all两页 keyset 无重复、无遗漏;archived精确过滤;- 只有确有下一页才返回 cursor;
- limit 65 在打开数据库前被 exact command validation 拒绝;
- secondary Project Owner 查询实例拓扑失败并写 denial audit;
- credential 在服务授权后漂移时,最终 inspect 事务拒绝且不提交 allowed audit;
- lifecycle、RoleBinding、防锁死、容量和 credential fence 回归;
- archived Project inspect 与 missing
- local-admin 与 local-owner-cli strict TypeScript 通过;
- 当时 runtime-core/local-sqlite 完整 TypeScript 受锁定的
croner、@types/semver、drizzle-orm本地安装缺失影响,未出现本切片类型错误; ADR-0218 后croner已移出 runtime-core/storage,ADR-0219 又删除@types/semverbuilder dependency;当前本机第一物化阻塞为drizzle-orm; - 不新增 migration、package、生产依赖或部署 contract 版本。