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

8.2 KiB
Raw Blame History

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_confirmationcluster 固定 separation_of_duty 且缺 quota authority 时 启动失败关闭。停止时先撤销新 mutation admission,再等待在途 mutation drain,最后 释放 authority。

2. 身份与 Policy

管理 mutation 只接受稳定 Userassurance 必须是 local_consolemulti_factorhardware,且数据库观察提交时距认证不超过五分钟并早于 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_dutyactivate 的 User 必须不同于目标 publication 的 publisher
  • deactivaterevoke 是收敛风险的操作,不要求与发布者不同,但仍必须通过强 认证、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/INSERTruntime、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 分别为 3ee48d1468569c9dc1fa9f04031a48a220161762d48eeac4cd924e2dcd7abd21486d46115e28e90604a47231fe95e3b1687649c063d93bf7ce267783f2a7165f
  • 真实 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 的精确收敛。