Files
qinglong/docs/adr/ADR-0172-immutable-model-price-quote-and-canonical-cost-settlement.md
T

7.6 KiB
Raw Blame History

ADR-0172:不可变 Model Price Quote 与 Canonical Cost Settlement

  • 状态:Accepted
  • 日期:2026-07-27
  • 关联:RFC D-12、D-13、D-157、D-160、D-161、D-162ADR-0167、ADR-0168、ADR-0170、ADR-0171

背景

ADR-0170/0171 已经建立不可变 usage ledger 与 provider I/O 前的 Project quota reservation,但费用仍有三个未封闭的权威裂缝:

  • provider 返回的 usage.costMicros 既不可验证,也可能缺失、使用不同币种或被 adapter 错误换算;
  • model policy 的人工 maxCostMicros 只能作为上限,不能证明调用使用了哪一版价格;
  • 价格变化后,如果只保存 model 和最终费用,就无法按历史 revision 重算、审计或精确重放;
  • Cluster 多副本可能在一次调用期间观察到不同价格,本机重启也可能丢失当时的计价输入;
  • 为价格目录、报价和结算各拆 package/service 会放大路由设备的 importer、制品与运维成本, 却没有独立部署或权限收益。

决策

1. Policy 必须选择一个精确、不可变的价格 revision

ModelInvocationPolicy.priceRevision 与 provider/model 共同选择 qinglong/model-price-catalog-entry@v1。条目固定:

  • USD
  • 每百万 input/output token 的整数 micro-USD rate
  • publisher time 与内容摘要;
  • provider、model、price revision 的精确身份。

启用单次费用上限或 Project cost quota 时,priceRevision 和精确 catalog entry 都是必需 条件;缺失必须在 provider I/O 前失败关闭。无费用约束且 priceRevision=null 的调用继续 允许 usage cost 为 nullable,不强迫所有部署启用计价。

当前 catalog 是有界、注入式 ModelPriceCatalogResolver。它冻结运行时消费契约,但不 宣称已经完成数据库 catalog、签名发布、激活、撤销或管理 UI;这些运营能力必须另行建立 权限和发布协议。

2. Provider I/O 前生成不可变最坏情况 Quote

Gateway 在 durable admission 前生成 qinglong/model-invocation-price-quote@v1。Quote 绑定 invocation、Project、model policy revision、provider/model、price revision、币种、input/output rate、catalog digest、 token 上限与独立 digest。

费用预留按 maxTotalTokensmaxOutputTokens 中合法且最昂贵的 input/output 分配计算, 不能用调用方估计值。每个 token 维度分别向上取整到整数 micro-USD,并用 BigInt 做中间 运算;越界失败关闭。Quote 的 reservedCostMicros 同时是单次费用 fence 和 Project cost quota reservation 的权威输入,替代人工复制的 reservation 数值。

3. Provider 报价不是账单权威

provider 返回 usage 后,Gateway 只信任经过既有上限校验的 input/output/total token。 usage.costMicros 无论是 null、错误还是任意值,都由 Quote 重新计算的 canonical cost 替换。返回给调用方、Completion、UsageLedger、QuotaSettlement 使用同一个 canonical usage。

qinglong/model-invocation-price-settlement@v1 绑定 Quote digest、Completion digest、 精确 token、canonical cost、settled time 与独立 digest。usage 缺失时不得创建零费用 Settlementunknown outcome 保留 Quote 和 quota reservation,但不伪造价格结算。

4. Quote 与 Settlement 进入既有原子事务

准入事务原子提交:

  1. StepRun ready → running、Run/Event/Mutation
  2. ModelInvocationStart
  3. PriceQuote
  4. 可选 QuotaReservation。

完成事务原子提交:

  1. terminal StepRun/Run/Event/Mutation
  2. Completion
  3. 可选 UsageLedger
  4. usage 存在时的 PriceSettlement
  5. 可选 QuotaSettlement。

exact replay 必须复验全部预期事实;Quote/Settlement 缺失、意外存在、digest 或字段漂移均 失败关闭。持久化 JSON 的一致性比较按字段进行,不依赖 SQLite TEXT 或 PostgreSQL JSONB 的对象键顺序。

5. 9004 保持可选、append-only 和双方言一致

不改写 9001/9002/9003。新增:

  • SQLite 9004-ai-model-pricing-snapshots
  • PostgreSQL pg-9004-ai-model-pricing-snapshots
  • PriceQuote/PriceSettlement 两张双方言表。

PostgreSQL ql3_runtime 只有 SELECT, INSERT,没有 UPDATE, DELETE;其它业务角色与 PUBLIC 不获得权限。Quote 与 Settlement 不是第二状态机,StepRun 仍是执行权威。

6. 不新增 package、依赖或后台服务

价格 contract、resolver 和计算留在已有 @qinglong/ai/pricingGateway、coordinator、 repository 与 Profile 复用现有 AI package。没有新增 workspace package或第三方依赖。

AI 禁用时不解析 catalog、不执行 90019004、不加载 provider/credential/storage,也不 创建 timer。edge/standalone 只在显式启用 AI 时把该模块纳入制品;Cluster 可注入未来的 数据库/配置发布 resolver,但不能把价格管理 authority 放进常驻 runtime。

本 ADR 不开放 HTTP/MCP/UI,也不把静态 resolver 解释为产品级价格目录管理。

被否决方案

  1. 直接信任 provider usage.costMicros:来源、币种、revision 与舍入不可验证。
  2. 只保存最终费用:价格变更后不能历史重算或证明计价输入。
  3. 调用完成后读取最新价格:一次调用可能跨 revision,Cluster 副本会产生不同结果。
  4. 用浮点美元计价:序列化与舍入会产生跨运行时漂移。
  5. 把 unknown usage 结算为零:允许预算绕过并伪造账单事实。
  6. 为 pricing 新建 workspace package/service:没有独立部署、依赖或权限边界,只会增加 路由器制品、lockfile importer 和 CI 成本。
  7. 立即把 catalog 管理写入 runtime 表:会把运营写 authority 与调用 runtime 合并。

当前验证

  • @qinglong/ai70 项测试,69 pass、1 条 PostgreSQL 条件 skip;真实 PostgreSQL 18 migration/runtime 分角色集成另 1 pass
  • SQLite 覆盖 exact revision、最坏情况 Quote、provider cost 覆盖、PriceSettlement、 quota 原子提交、exact replay,以及 priced unknown recovery 保留 Quote/全额 reservation 且不创建 Settlement
  • PostgreSQL 真实集成覆盖 JSONB 回读、9004 DDL/ACL、Quote/Completion/Ledger/ PriceSettlement/QuotaSettlement 同一事务;
  • SQLite 9004 checksum 为 572e37d2f44df43a50b51a07c1b4b0bb87fbb22e9cafbd3421ec7ab250036951 PostgreSQL 9004 checksum 为 d38b12c2640fdd9fe21dc43a4743fb3480c988fa0a87e210fd81074d87569d2f
  • QL3 22 个 package importer 的完整拓扑 build/test 退出 0dependency audit 覆盖 @qinglong/ai 16 个源码文件且 findings=[]
  • edge-ai 为 4,363,769 bytes/512 files/41 loaded modulesstandalone-ai 为 4,363,841 bytes/512 files/41 modules,均低于 5 MiB/640 filesdisabled AI 只加载 1 个模块、RSS 增量 425,984 bytes,三种 Profile 的 storage/provider loader 均为 0
  • PostgreSQL 18.4 arm64 physical HA 在 timeline 1→2 promotion 前后精确复验八张 ql3_ai 表、90019004 history/checksum 和八表 runtime append-only ACL optionalAiFeatureSchemaSurvivesPromotion=true 且总 passed=true

后续门禁

  1. 建立签名或受权限保护的 durable catalog publisher、activation/revoke 与配置 ceremony
  2. 明确非 USD 币种、汇率 revision、cached-input/批处理等 provider-specific 计价维度;
  3. 增加 Quote/Settlement 数据行级 COMMIT-response-loss、promotion 与并发 revision 切换 fault,而不只验证 schema promotion
  4. 完成认证、Project Policy、rate limit、低敏 audit 的 pricing/usage/quota API、CLI/UI
  5. 完成不可变 rollup、coverage receipt、retention 和磁盘耗尽证据。