Files
qinglong/docs/adr/ADR-0174-authorized-model-price-catalog-management-facade.md
T

156 lines
8.2 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-0174:授权型 Model Price Catalog 管理 facade
- 状态:Accepted
- 日期:2026-07-27
- 关联:RFC D-49、D-73、D-84、D-85、D-156、D-162、D-163、D-164ADR-0142、ADR-0143、ADR-0144、ADR-0145、ADR-0173
## 背景
ADR-0173 已把价格目录冻结为不可变 publication 和 append-only generation head
但低层 repository 仍要求调用方直接填写 `publishedByUserId`/
`changedByUserId`。这足以证明数据来源字段,却不足以证明:
1. actor 是仍有效且最近完成强认证的稳定 User;
2. 一个受信平台 Policy 确实允许本次全局价格配置 mutation;
3. cluster 的发布者没有自行激活自己的价格;
4. rate-limit/quota 使用稳定幂等键;
5. 授权事实和 publication/head 在崩溃、并发与 COMMIT response loss 下同成同败。
价格目录是跨 Project 的基础设施配置。把它绑定到任意 Project 的
`project.manage` 会制造虚假的租户边界;把它放进 provider credential authority
又会把 Secret 生命周期与计价权威合并。
同时,`local-admin` 是默认本机产品闭包的一部分,`cluster-admin` 还承载通用集群
管理任务。让这两个包静态依赖可选 `@qinglong/ai`,会使禁用 AI 的路由设备和管理
镜像仍安装 AI,违反 D-156 的零成本关闭语义。
## 决策
### 1. 管理服务留在现有 AI 能力边界
新增 `@qinglong/ai/price-catalog-management`,不新增 workspace package、生产依赖、
进程、timer、watcher、缓存或数据库连接。它只组合:
- `ModelPriceCatalogAuthorizedAdministrationRepository`
- 显式注入的 platform-level authorizer
- 可选、调用方驱动且以 authorization ID 幂等的 quota port
- profile 决策模式。
通用 `local-admin`/`cluster-admin` 不反向依赖 AI。后续产品 transport 必须只在 AI
显式启用的短生命周期 composition root 中惰性装配现有认证、私有 command-file 或
HTTPS transport port。
现有 `@qinglong/ai/profile` 同时提供管理 capability 的禁用优先 composition root
不再拆分新包。禁用态不调用 authority loaderedge/standalone 固定
`human_confirmation`cluster 固定 `separation_of_duty` 且缺 quota authority 时
启动失败关闭。停止时先撤销新 mutation admission,再等待在途 mutation drain,最后
释放 authority。
### 2. 身份与 Policy
管理 mutation 只接受稳定 `User`assurance 必须是 `local_console`
`multi_factor``hardware`,且数据库观察提交时距认证不超过五分钟并早于
principal expiry。User ID 从 principal 派生,HTTP/CLI body 不得覆盖。
authorizer 返回 domain-separated、digest-bound 的 allow/deny decision,固定绑定
platform policy revision 与有界 reasons。该 Policy 是平台级 authority,不伪装为
某个 Project 的角色绑定。
### 3. Profile 决策模式
- edge/standalone 使用 `human_confirmation`,允许同一强认证 Owner/User 发布并在
独立 mutation 中激活;
- cluster 使用 `separation_of_duty``activate` 的 User 必须不同于目标
publication 的 publisher
- `deactivate``revoke` 是收敛风险的操作,不要求与发布者不同,但仍必须通过强
认证、Policy、quota 与 expected-generation/head fence。
职责分离在 service 预检查一次,并在 repository transaction 内从不可变 publication
authorization 再检查一次,不能由自定义调用方绕过。
### 4. 9006 授权事实
不修改 90019005。独立双方言 migration
- `9006-ai-model-price-catalog-authorizations`
- `pg-9006-ai-model-price-catalog-authorizations`
新增单张 append-only authorization 表。每条事实固定绑定:
- authorization/request ID 和操作;
- provider/model/nullable revision
- catalog command digest
- publication digest 或 head digest(二选一外键);
- User、authentication ID/assurance/lifetime
- Policy revision/decision digest/reasons
- decision mode、数据库提交时间和 authorization digest。
`publishAuthorized`/`transitionAuthorized` 必须在原 publication/head 事务内插入授权
事实。catalog command digest、result digest、authorization command digest 均唯一;
精确重放返回同一事实,任一 ID 或语义漂移冲突。已有低层 raw mutation 如果没有
authorization,不得由新入口事后补写“授权”;旧 publication 若需启用,必须使用新
revision 经正式 ceremony 重新发布。激活还要求目标 publication 已有 publish
authorization。
PostgreSQL `ql3_admin` 只获得授权表 `SELECT/INSERT`runtime、PUBLIC 与其它业务
角色无权,任何角色都没有 `UPDATE/DELETE`。runtime 解析价格仍只读取 9005 的 active
publication/head,不加载授权 JSON。
### 5. 失败语义
- 弱、过期或非 User principalauthentication error
- Policy denyauthorization error
- authorizer/quota 不可用:unavailable,且目录 mutation 为零;
- quota 超额:保留有界 retry-after
- 同人 cluster 激活:separation-of-duty error
- raw publication、缺失授权事实、stale generation/head、摘要漂移或并发输家:
conflict
- 数据库在实际提交前再次验证 principal lifetime,进程时钟的预检查不能替代数据库
事实。
认证失败、Policy deny、quota deny 的 transport-level 审计仍由后续产品入口完成;
本 ADR 只把成功授权事实与业务 mutation 原子化,不能据此宣称 HTTP/CLI/UI 已开放。
## 拒绝方案
1. **把价格目录当成某个 Project 的配置**:共享价格会被虚假租户 fence 控制,拒绝。
2. **只信任 body 中的 User ID**:不能证明认证与 Policy,拒绝。
3. **先写通用 security audit,再写 catalog**:两个事务留下审计与 mutation 裂缝,
拒绝。
4. **在 9005 原表加列**:破坏已发布 checksum 和 migration 历史,拒绝。
5. **允许 raw publication 后补 authorization**:会把事后批准伪装成原子授权,拒绝。
6. **cluster 发布者自行激活**:价格配置缺少独立复核,拒绝。
7. **让 local-admin/cluster-admin 静态依赖 AI**:污染禁用闭包和低资源部署,拒绝。
8. **为管理 facade 新拆 package/service**:没有独立部署收益并增加运维成本,拒绝。
## 当前证据
- AI 全量 89 项:87 pass2 条真实 PostgreSQL 条件 skip;新增 Profile 测试证明
edge 禁用零 loader、standalone 惰性人工确认装配、cluster 缺 quota 失败关闭,以及
cluster 固定职责分离;
- SQLite 已覆盖原子 publication/head/authorization、exact replay、authorization ID
漂移、raw publication 拒绝、双层职责分离、FK 与 rollback
- PostgreSQL 18 migration/admin/runtime 三角色专项测试覆盖 authorized publish、
exact replay、双人 activation、授权读取、runtime deny 与 admin append-only ACL
- 90019005 checksum 未变;9006 SQLite/PostgreSQL checksum 分别为
`3ee48d1468569c9dc1fa9f04031a48a220161762d48eeac4cd924e2dcd7abd21`
`486d46115e28e90604a47231fe95e3b1687649c063d93bf7ce267783f2a7165f`
- 真实 PostgreSQL 专项目录测试 2/2 passModel Invocation 串行门另 1/1 pass。
- PostgreSQL 18.4 arm64 physical HA 门已证明 90019006 history/checksum、11 张
`ql3_ai` 表及 9006 runtime deny/admin append-only/其它角色 deny ACL 在 timeline
1→2 前后完全一致,`optionalAiFeatureSchemaSurvivesPromotion=true` 且总门通过;
- 22-importer dependency audit 覆盖 AI 20 个源码文件且 `findings=[]`,通用
local-admin/cluster-admin 仍不依赖 AI
- disabled benchmark 在 edge/standalone/cluster 的 storage/provider/management
authority loader 均为 0,管理禁用启动最大 0.265 ms,模块加载 RSS 增量 425,984
bytes;默认 edge/standalone 产物仍为 4 MB 以下、478 files 且不含 AI,显式
edge-ai/standalone-ai 为约 4.54 MB、520 files,均通过各自门限。
## 后续门禁
1. 本机私有 command-file transport、local-console authentication 与失败审计;
2. Cluster TLS/identity assertion transport、平台 Policy repository、耐久 quota 与
deny/unavailable 审计;
3. transport body 中不得接受 principal/Policy/result digest
4. COMMIT response loss 注入覆盖 authorization 与 catalog result 的精确收敛。