ADR-031:执行、计量与账务终局协议
冻结 Execution、Gateway、Metering、Billing 与 Operations 之间的事实所有权、线性化边界和迟到结算处置
状态:已采用,待 Contracts 与运行时实现 日期:2026-09-04
背景
OceanWay 已确定采用同级 Text Gateway Pool 与 Media Gateway Pool,并由 Core 统一拥有 Run、客户计费与产品事实。仅有“Gateway 返回 Usage,Core 扣费”仍不足以处理以下真实情况:
- 一个 Run 可能因为安全重试、故障切换或人工恢复拥有多个 Execution Attempt;
- Gateway 的 Usage/Cost 可能缺失、迟到、冲突或在 Provider 查询后更正;
- 图片、视频、文本和工具调用具有不同 Charge Dimension,不能用单个 Run 状态代表全部计量输入;
- Billing、Execution、Metering 与 Gateway 分属不同事务 Owner,无法依赖跨服务锁或分布式数据库事务;
- Reservation 释放后仍可能出现可信正差额,既不能静默丢弃,也不能绕过预算自动扣成负余额;
- 管理员处置必须基于不可变证据和一次性授权,不能通过后台按钮直接改数据库。
本 ADR 冻结跨域协议和并发边界。各对象的完整严格字段以钱包与商业系统、调度与网关架构和管理员平台与运维控制面为唯一规范;本页不另造简化 DTO。
决策摘要
OceanWay 正式采用以下单向事实链:
核心结论是:
- Gateway 只拥有供应侧执行与不可变 Evidence,不生成 OceanWay 规范计量事实。
- Metering 是
MeterEvent、ProviderCostFact、Eligibility 与SettlementInputSnapshot的唯一 Writer。 - Execution 以不可逆 CAS 关闭 Run 的 Attempt 接纳边界,并冻结完整 Attempt 集合。
- Billing 在调用外部 Owner 的版本化验证接口后,只在自己的 Repository 内原子写 Decision、Ledger 与 Reservation 终态。
- 成功终局是吸收态;验证边界之后到达的新事实进入追加式 Transition 或 Late Settlement,不重开原 Finalization。
- Operations 拥有 Reconciliation Case 工作流,但不拥有账务事实,也不直接执行扣款。
事实所有权
| 事实或能力 | 唯一 Owner / Writer | 其他域如何使用 | 禁止行为 |
|---|---|---|---|
RunAdmissionManifest、Reservation 准入绑定 | Core Admission / Billing 同一受控准入事务 | 通过版本化 Read Contract 和内容摘要引用 | 按 Ref 读取“最新版本”或用当前配置重建历史 |
AttemptExecutionManifest、Attempt 接纳状态 | Execution | Gateway、Metering 与 Billing 只持有不可变四元组 | Gateway 自建 OceanWay Attempt;关闭后新增 Attempt |
AttemptRouteBinding、GatewayRouteSnapshot、Provider Evidence | 实际 Text/Media Gateway | Metering 通过定向 Read/Validate Contract 读取 | 返回裸私有路由正文;Gateway 铸造 MeterEvent |
ExecutionEligibilityFact、AttemptNotDispatchedFact、ExecutionFailureFact | Execution | Metering通过版本化精确 Read Contract消费完整低敏事实 | Metering重建或改写执行终态;Gateway自报可结算资格;用 Operations 错误记录代替失败事实 |
EligibilityDecision、MeterEvent、ProviderCostFact、SettlementInputSnapshot | Metering | Billing通过版本化 Current Validation Receipt消费 | Billing拼接多次无边界 Current查询;Observation驱动扣款 |
RunAttemptSetManifest、RunExecutionFinalizationFact | Execution | Billing 获取唯一关闭事实 Receipt | Billing 关闭 Attempt;跨库锁定 Execution Row |
| Finalization Decision/Committed Fact、Ledger、Reservation 终态、Settlement Transition、Late Settlement Exposure | Billing | Operations 只消费低敏 Outbox,并用完整四元组调用 Owner Read | Admin/Operations 直接改账;用 Case 状态代替账务状态 |
| Reconciliation Case、审批/JIT 工作流、处置时间线 | Operations / Admin Control Plane | Domain 只接受 Core 签发的一次性执行 Grant | Case 关闭事务假装与 Billing Ledger 跨库原子 |
准入与执行身份必须内容寻址
RunAdmissionManifest 自身使用 Ref、Schema Version、Digest Algorithm Version 与 Digest 四元组内容寻址;AttemptExecutionManifest 和 RunExecutionToken@1 必须逐项携带并验证该四元组。Attempt 另外固定自己的 Manifest 四元组、实际 Model Deployment、Gateway Deployment、Routing Policy Revision 与 Output Contract 四元组。
Core 的版本化 Manifest Read Contract 必须验证精确 Workload Audience、Tenant、Run、Schema、算法、Repository 不可变绑定与重算摘要。不存在只传裸 Ref、按 Ref 读最新版本、宽松解析或失败后读取当前 Offering/Policy 的兼容路径。
run.created@2.0 与 Run Manifest 的等值由 Admission Producer 在同一事务 Append 前保证;任一错配回滚 Run、Manifest、Reservation、Event 与 Outbox。Operations Projector 只消费自包含 Event,不跨服务读取 Manifest 来补值或重复判断这项不变量。
Attempt 关闭是 Execution 的线性化边界
Execution 的 Run 只有以下合法顺序:
accepting_attempts
-> 在同一 Execution 事务锁定全部 Attempt 与唯一 ExecutionEligibilityFact
-> 写 RunAttemptSetManifest@1
-> CAS 为 attempts_closed
-> 写 RunExecutionFinalizationFact@1
-> 写 execution.run-finalization.closed@1 + Billing Mandatory DeliveryRunAttemptSetManifest@1 明确按(runStepId, executionAttemptId)保存完整 Attempt Manifest 与 Execution Eligibility Fact 四元组;空集合也必须以 attemptCount=0 和空数组表达。RunExecutionFinalizationFact@1 必须与该集合逐项、逐序相等。Closure Event完整携带Account/Reservation/Run、Admission Manifest、Finalization Fact与Attempt Set四元组,并把billing_finalization_coordinator作为Mandatory Destination;关闭CAS、Fact/Event/Outbox任一失败全部回滚。关闭后没有 reopen、supersede 或新增 Attempt 分支,响应或同步调用Billing丢失也由Outbox恢复。
首期Execution、Metering与Billing是oceanway-core内共享一个PostgreSQL的逻辑模块;既有Core Admission单一本地事务同时创建Run/Manifest、Reservation、两条准入Event、唯一BillingFinalizationFence + Durable Work,任一失败全部回滚,不引入跨库Saga。Fence、内容寻址Trigger Snapshot/Work与append-only Work Attempt使用严格Contracts:等待态不能夹带Work,pending_evaluation必须绑定最新Generation唯一Work,旧Generation/Fencing Token不能提交经济结果。Closure Event和Settlement Input Event以Mandatory Delivery单调推进Closure/Dimension Watermark并生成新Work Generation;每代billingFinalizationOperationId由Reservation/Run/Generation和内容寻址Trigger Snapshot确定性派生,并可由精确Operation Read在崩溃后恢复原Work。Admission首代Work在Closure Watermark尚未接受时只能以closure_event_not_applied阻塞;Owner Read不能绕过Event提前终局。Billing只在Closure Event/Consumer Receipt已耐久应用后调用readRunFinalizationPrecondition,并要求其closed结果与Event中的Finalization Fact、Attempt Set、State Version与Count全等,之后才构造Input Manifest并取得Current Receipt。成功事务再次锁定Fence,将Closure Event/Receipt身份写入Committed Fact并与Reservation一同推进终态,因此后到Mandatory Delivery不会被Committed Fence卡死。not_closed/run_not_found 只是该次Work结果,不能伪造空 Input Manifest、成为永久负面事实或终结Fence。Billing 不绕过Owner Read Contract直接Join Execution表,也不把“某次查询没有发现新 Attempt”当作关闭证明;未来若拆分Billing数据库必须另发ADR。
reconciliation_required同样不是无入口/无出口状态。首次判定前,Billing按Run级sourceKind=billing_finalization向Operations预留内容寻址的Case Identity;预留只分配稳定Ref,不创建可见Case,响应丢失以确定Operation读取原Identity。Identity只冻结Tenant、Account/Reservation/Run/Fence/Generation、Case Request Operation及Predecessor,不绑定可能被新Watermark抢占的Triggering Work/Finalization Operation;该Reserve/Read只授权相同精确Billing Finalization Case Identity Workload Scope,禁止Admin、latest、列表、裸Ref或跨Source调用。若G1预留后被G2抢占,G1因Fence CAS失败而零提交,G2必须复用原Identity,不能因Work变化让同代预留冲突或新建第二Case。Billing随后在同一事务提交Reconciliation Decision、绑定Identity的Fence/Work Finish和billing.finalization-reconciliation.case-requested@1,由该Event显式冻结最终获胜的Work完整四元组、独立triggeringBillingFinalizationWorkGeneration与Finalization Operation,再由core_operations Mandatory Delivery按预分配Ref幂等创建Case。Operations先验证本地Identity,再通过Event绑定的readBillingFinalizationDecisionForOperations重算Decision与Triggering Work/Generation,不能从不可逆Operation哈希推测Generation、复用仅授权Billing Worker的Work读取或盲信Event摘要。Operations只编排调查与授权,不能本地关闭业务Case。
这条出口必须和可能产生Reconciliation的运行时同批启用,不能只有Case而没有命令。固定构造顺序是:严格FinalizationReconciliationResolutionCommandRequest@1 → 通用Impact Preview/Approval/JIT与AdminCommandCandidate@1 → Operations-owned FinalizationReconciliationResolutionDecision@1 → Core签发Audience为billing_finalization_reconciliation_resolution_command的单次SignedWorkforceCommandExecutionGrant@1 → Billing Applied Fact/Event。Request/Candidate/Decision不含后继Grant、Applied Fact或Operation;resolutionOperationId只由完整Decision四元组派生,Grant随后绑定全部对象和业务身份,因而摘要链无环。唯一Action是reevaluate_current_owner_state,Impact Preview必须明确无直接Ledger/Reservation效果,命令也禁止金额、替代Snapshot、忽略冲突或自由Map。命令中的非空investigationEvidenceReferences[]只是Operations调查与授权依据的内容寻址引用,Billing只验证它在Request/Candidate/Decision/Grant/Fact之间精确等值,不用泛化裸Ref Read把它当作Owner已修复证明;真正的安全判断来自后继Work重新执行既有Execution/Metering/Gateway严格读取,仍冲突就创建新Case代次。
Billing在验证当前Case代次、Identity/Request、Resolution Decision、一次性Command Grant及调查引用集合等值后追加FinalizationReconciliationResolutionApplied@1及billing.finalization-reconciliation-resolution.applied@1,冻结billing_finalization_coordinator + core_operations两条Mandatory Delivery;Fact内的actorWorkforcePrincipalId必须与Decision Actor、Grant sub、Command Candidate、Audit及Event Envelope Actor相等。每个Case Generation只允许一次Resolution Applied:expectedResolutionRevision=resolutionRevision=1;Event Aggregate ID由billingFinalizationFenceId + reconciliationGeneration确定性派生且aggregateRevision=1,重评仍冲突必须创建下一Generation。Grant Exchange同样先按CommandGrantExchangeResult@1.commandGrantExchangeOperationId恢复:该Operation只由逐字节等于Actor Assertion JWT iss + jti的actorAssertionIssuer + actorAssertionJti派生,Candidate/Workload/Request作为Result正文insert-or-compare;完全相同输入在Core已提交后返回首次Grant,异正文冲突,仅not_found检查当前Assertion时间/JTI。Resolution命令重投再先按resolutionOperationId读取已提交Fact并完整比较Request/Candidate/Decision/Grant,只有not_found才重新检查当前Grant时间/JTI并写入,避免两层响应丢失分别使授权或已提交结果永久卡住;Applied Fact本身就是稳定Result。Coordinator把Case/代次/Revision、Fact与Event摘要写入Fence的Resolution Watermark,并与Consumer Receipt、新Trigger/Work原子提交;Operations只有通过Event绑定的readFinalizationReconciliationResolutionAppliedForOperations重算同一Billing Fact后才关闭Case。Fence处于reconciliation_required期间,新到Settlement Owner Event只能原子推进Watermark并返回严格held_for_resolution Receipt,不得创建Work、离开当前Case或让新输入抢先开启下一代;Execution Closure在进入该状态前已经唯一且不可逆地应用,重投只返回首次Receipt。只有当前Case代次的Resolution Applied Event能够把期间累积的全部Watermark纳入新Trigger并重开评估。该Event只允许重新评估,不能携带金额、改账、释放Reservation或覆盖Owner事实。重评仍冲突时必须进入新Case代次,旧Grant/Event不能复用;同代Resolution已经应用或已由其重评进入后继代时,晚到重复Event才以严格already_superseded Receipt收敛而不创建第二Work。这一单入口规则避免Case Requested已经提交、但投影尚未创建Case时被Owner Event绕过而留下永久孤儿Case。
两条Mandatory Delivery没有隐式先后:Resolution Applied可能早于同代Case Requested投影,重评产生的下一代Case Requested也可能早于前代Resolution投影。Operations必须把已严格验证但尚无Case的Resolution保存为Pending;后到同代Request时直接物化终态。后继Request必须验证直接前代Identity,若前代尚未被对应Billing Resolution Fact权威终结,则保存为Pending Successor,不能同时创建两个Open Finalization Case,也不能丢弃或隔离合法乱序;前代关闭与后继Case创建必须在同一Operations事务收敛。在线消费和Shadow Rebuild对Resolution→Request、C2 Request→C1 Resolution→C1 Request等排列必须得到同一结果。
两种执行分支都必须显式
正常进入 Gateway 的 Attempt 必须形成唯一 AttemptRouteBinding + GatewayRouteSnapshot,并由 Gateway 维护 Usage/Cost Availability 与 Evidence 的不可变 Current 链。一个 OceanWay Attempt 内不得更换 Channel、Supply、Credential Version、Provider Model 或 Adapter;需要换路时由 Execution 创建新 Attempt。
在 Provider Side Effect前发生的确定性 Gateway拒绝不能伪造 Binding、Route或零 Usage。Gateway先按 Attempt + Gateway Deployment + Attempt Manifest四元组 reserve唯一 Dispatch Slot,并在同一事务把 Slot从 reserved原子推进为 bound或 terminal_rejected;两个终态吸收且互斥,重试只能返回原 Binding或原 Rejection。Gateway追加严格 GatewayPreBindingRejectionFact@1后,Execution验证 Slot终态,先追加 AttemptNotDispatchedFact@1,再由 Execution自己形成引用该事实、dispatchDisposition=not_dispatched 的唯一 ExecutionEligibilityFact@1;Metering只能读取两者并据此追加 Ineligible EligibilityDecision和 Terminal-none SettlementInput,不能成为 Execution Eligibility Writer。该分支以明确 Ineligible/Terminal-none证明目标额为零,不拥有 Gateway Availability,因此 Billing禁止为它请求或夹带 Gateway Validation Receipt;需要继续执行只能创建新 Attempt。
一旦 Provider Side Effect 可能已经发生或提交状态未知,就必须走 Attempt-bound 分支,等待查询、恢复或进入 Reconciliation,不能降格成 Not-dispatched。
Execution追加唯一ExecutionEligibilityFact@1时,同事务追加execution.attempt-eligibility.finalized@1并创建metering_input_processor Mandatory Delivery;Gateway每次原子推进Attempt-bound Availability Current/Active Evidence Head时,同事务追加gateway.evidence-availability.current-changed@1并创建相同Mandatory Delivery。Metering Consumer把两类Event分别写入按Attempt唯一的MeteringAttemptInputFence Watermark和带Fencing Token的Durable Work后才Ack:Usage Work等待Execution Fact与Gateway Usage Head(Not-dispatched只需前者),Provider Cost Work只依赖Cost Head。任一输入先到都耐久等待,另一输入或后继Head到达都会生成新Trigger Generation。Worker通过Event绑定的精确Owner Read读取Execution Eligibility、Attempt Manifest、Binding/Availability/Evidence,禁止依赖同步调用或best-effort Observation。
Gateway Source Read与Current Validation分成两阶段:Metering首读只凭已接受Gateway Event的完整身份/Envelope摘要和其中精确对象四元组获得不可变Availability/Evidence/Route Cost正文,不要求尚未构造的Basis/Candidate/Operation;随后才由正文确定性构造Basis或Candidate并以Purpose-bound Operation取得Current Receipt。Bootstrap授权不能签Receipt,Validation授权不能枚举或替代首读。
Usage Work只有在全部Charge Dimension的Eligibility/MeterEvent/SettlementInput及其下游Mandatory Event同事务提交,且Fence Watermark未被后继超越时才完成。Cost Work对五种Availability都有严格收敛结果:available原子写ProviderCostFact与双Receipt;pending结束本代并等待后继Head;not_reported|unavailable追加内容寻址的“当前无成本证据”处理事实但绝不伪造零成本;conflicting写ProviderCostAvailabilityProcessingFact@1.outcome=reconciliation_required,以该Fact四元组作为Metering-owned耐久对账锚点。当前13-Pair Delivery Set没有Provider Cost Processing Event,因此本阶段只保证Metering定向诊断与明确标注为不完整的best-effort告警,不声称Operations有完整队列,也不创建未定义的第三种Reconciliation Case;Owner修复只能由Gateway追加后继Availability Current Event,再创建新Generation处理。可靠Admin队列必须以后续Canonical Processing Event、Mandatory core_operations Delivery、Owner Read和投影契约为前置。每种结果都通过对应Cost Lane Trigger CAS与append-only Finish Fact提交,未来Head再创建新Generation。Event Ack后Worker崩溃、A2/E2提交后响应/Observation丢失且无后续Poll、两输入乱序和旧Lease晚到都必须由Inbox/Work恢复。GatewayDiagnostic继续只用于诊断,不能推进Metering Fence或驱动结算。
上述五态只描述Gateway权威Availability本身。Owner Read暂不可用、网络/锁/容量故障不写Domain Fact、Finish或CAS;已证明的schema/identity/mapping/normalization/receipt/repository错配则写内容寻址ProviderCostProcessingConflictEvidence@1 + MeteringInputWorkAttemptBlocked@1,隔离Source并保留同Work/Operation/Generation为Active Blocked,修复后重试。Provider Cost Lane的Blocked外层只允许provider_cost_processing_conflict;Evidence内部再区分Gateway Evidence、Gateway Binding或携带Stage与冲突Candidate摘要的本地确定性校验。它们不得冒充ProviderCostAvailabilityProcessingFact.reconciliation_required,也不创建Operations Case。
若A1 Work已经Started而A2 Current Event推进同Lane,新Generation事务必须以旧Work/Started/Fencing Token追加唯一MeteringInputWorkAttemptFinished.outcome=superseded,或由旧Worker晚到后在确认Current新代时insert-or-compare同一Fact;两条路径都不得用旧Token写业务结果。扫描器看到该Finish立即停止恢复旧Attempt。Contracts与跨进程测试必须覆盖A1 running → A2 event → A1 late finish竞争,并证明新Lane不被旧Worker覆盖。
Metering 先形成单一结算快照
每个 Attempt × Charge Dimension 都由 Metering 形成不可变、可追加更正的 SettlementInputSnapshot@1 Current 链。Snapshot 将以下内容固定为一个一致输入,而不是让 Billing 分别读取后自行拼接:
- Execution Eligibility Fact;
- Current Eligibility Decision 与规则版本;
- Current MeterEvent Head 或明确的 none;
- Gateway Usage Basis,或 Not-dispatched Fact;
- Attempt、Binding、Route、Evidence 与执行资格身份;
- Pricing、Billing Policy、严格结算经济身份、数量、单位、目标净额与 Reconciliation 状态。
Billing 对每个维度调用 validateSettlementInputCurrent,Receipt 精确绑定 Snapshot 四元组、Purpose、Finalization Operation 与 Input Manifest Digest。Receipt 不使用隐含 TTL;验证线性化点之后到达的新 Head 是 post-boundary correction,而不是追溯宣布旧 Receipt 从未有效。
每个Snapshot/Current Pointer事务还必须原子追加封闭metering.settlement-input.current-changed@1 Event,并为billing_settlement_catch_up创建Mandatory Delivery。Event以Contracts确定性Settlement Dimension ID为Aggregate、以Snapshot State Version为Revision,完整携带Tenant/Account/Reservation/Run/Step/Attempt/Dimension、严格经济身份、Current Snapshot五元组和直接前驱五元组;弱“changed=true”通知不合法。Billing Consumer在成功Finalization前必须于单一Inbox事务推进SettlementInputWakeupWatermark:Fence处于普通等待/评估态时同时冻结Trigger并产生/合并Work;处于reconciliation_required时只写绑定当前Case/Generation/Fence Revision且明确无Work的held_for_resolution Receipt。成功Finalization后则推进Dimension Catch-up Fence/Work;只有Watermark、所选Fence动作和Consumer Receipt原子提交后才Ack。预期Fence缺失返回retryable且不写Receipt,绝不能把早到Event停在无Work的awaiting_finalization。
成功Finalization事务为Manifest每个维度原子创建唯一BillingSettlementCatchUpFence@1与Durable Work Item,并锁定或初始化已存在的Watermark;事务末精确集合约束保证Dimension、Fence和Work一一对应。Watermark、Fence、Basis、内容寻址Work、Started/Finished与Operation Read均使用严格Contract和Fencing Token。首个Work总是执行一次Owner Current验证,即使Watermark尚未领先,避免事件仍在路上。Catch-up Worker按purpose=billing_catch_up_fence + BillingCatchUp Basis带算法摘要 + 待验证Snapshot五元组确定性派生billingCatchUpValidationOperationId并调用新增的严格validateSettlementInputCurrent Purpose分支。not_current只提供Current五元组,Worker随后精确读取完整Snapshot/Lineage并走正常post_finalization_transition;Transition应用或发现已消费时,必须在同一事务重基线Fence并创建下一代pending_verification Work,不能直接冒充caught up或清空Work等待不存在的新Event。只有后继Work取得专用current Receipt,才可在Billing本地事务确认本地高水位不领先且State/Fence Revision未变后把Fence标记caught_up。更高Watermark在该事务前到达会阻止完成,在事务后到达会重新打开Fence并创建Work。
因此S1 Receipt → S2 Snapshot/Event先于Finalization到达并Ack → Finalization按S1提交 → 无S3也必须由“首Work应用S2 Transition + 原子创建后继验证Work + 后继Current Receipt”最终追平S2。Metering Event未完成的Mandatory Delivery、Billing Pending Fence/Work或Watermark领先任一状态都必须可观测并阻止声称完全追平;Retention必须保留其引用的Event/Snapshot/Receipt。Contracts发布Event、Catch-up Purpose/Receipt和Watermark/Fence边界DTO,跨进程测试覆盖早到/晚到、Ack后崩溃、Finalization事务失败、首Work恢复、Transition与Successor Work原子回滚/响应丢失、Receipt与新Snapshot竞态、S2→S3合并追平及“无S3”反例。
ProviderCostFact 与客户结算独立:它只进入供应成本、对账与毛利分析。供应成本缺失、迟到或更正不得独自触发客户扣款,也不得阻断已经由规范 MeterEvent 与冻结 Price Snapshot 证明的客户费用。
Billing Finalization 使用三组完整验证
Billing 先按 Contracts 的封闭 FinalizationInputManifest@1 Schema 内容寻址地保存输入。它必须引用 Execution 关闭事实和 Attempt Set,完整枚举该集合中每个 Attempt 的 Contracts 注册 Charge Dimension、Current SettlementInputSnapshot、严格 Resolution/Billing Evaluation、相同 credits | entitlement{entitlementKey,unit} | money{currency} 经济身份及规范汇总。成功终局前再取得并冻结:
- 恰好一份 Execution Receipt:证明 Run 已不可逆关闭,Attempt Set 与 Input Manifest 一致;
- 完整 Metering Receipt 集:与全部 Attempt × Charge Dimension/Snapshot 一一对应;
- 去重后的 Gateway Receipt 集:只覆盖 Input Manifest 中实际存在的唯一 Attempt-bound Availability Snapshot;Not-dispatched 维度既不计入也禁止夹带。
三组 Receipt 进入 Contracts 封闭的内容寻址 FinalizationValidationBundle@1;每个 Entry 必须显式回显 Purpose、Operation、Input Manifest 四元组和被验证对象身份,三组集合与 Manifest 精确等值,不能用自由 Map、Count 或“已验证”布尔值替代。链路固定为 Input Manifest → Receipts → Validation Bundle → BillingFinalizationDecision,Receipt 不反向引用 Bundle,避免摘要自引用。
只有全部维度均为 metered | terminal_none、Eligibility 已决、无 Pending/Conflicting/未决 Case、目标净额不超过可结算预占且三组验证完整时,Decision 才能是:
settle_and_release_remainder:结算目标净额并释放余量;release_all:由显式零目标证据释放全部预占。
Execution 未关闭或输入尚未封闭时只能 blocked;输入冲突、超预占或存在未决 Case 时只能 reconciliation_required。这两类评估不得写 Ledger 或终结 Reservation。
不使用跨服务事务
成功 Decision、将所有维度调整到目标净额的 Ledger Entries、Reservation终态、每维度 SettlementInput Consumption/Dimension State、Catch-up Fence/Durable Work与余量 Release,只在 Billing自己的 Repository中锁定 Billing Account/Reservation/Dimension State,并与内容寻址的 BillingFinalizationCommitted@1、Audit、billing.finalization.committed@1、Outbox Append和冻结 Delivery Set原子提交。Committed Fact完整绑定 Decision/Input/Validation、全部 Consumption/State、Ledger/Release和提交后 Reservation;Operations只凭 Event四元组调用 readBillingFinalizationCommitted验证。任一子分录、Consumption、Fence/Work、Committed Fact、Audit、Event或 Outbox失败必须全部回滚;已提交的 Execution关闭事实和外部验证 Receipt保持不变,原 Operation可以确定性重试。
Billing 不在本地事务中锁定或再次读取 Execution、Gateway、Metering、Operations Case、Approval 或 JIT 的“当前 Row”,也不宣称这些数据库与 Ledger 同时提交。外部 Owner 用版本化、内容寻址、Purpose-bound 的 Receipt 提供线性化证据;本地 Owner 用 CAS、唯一键和 insert-or-compare 保证自己的原子性。
成功 settle_and_release_remainder | release_all 与 Reservation 终态是吸收态。成功后禁止追加后继 Finalization Decision、重开 Reservation 或修改 Base Settlement。
终局后变化采用追加式 Transition
成功终局后的 Current Eligibility、MeterEvent 或 Evidence 变化先形成 BillingSettlementTransition:
- 负差额按冻结 Policy 追加 Refund;
- 可获准的正差额追加 Adjustment;
- 不能自动处理的正差额形成 Billing-owned
LateSettlementExposure@1,并通过 Outbox 打开 Operations-owned Reconciliation Case。
首期 lateEligibleDisposition = reconcile_before_charge。Reservation 已释放后的正差额不能自动扣成负余额,也不能被静默当作供应损失。Billing先向 Operations预留稳定 Case身份;同一次 opening | open暴露沿用该 Case与 Generation,一旦该代进入 resolving,之后的新正差额必须使用 caseGeneration + 1、新的 Identity Reservation与新的 Case Ref,旧代授权和回执不能复活新代。人工执行需要同时满足:
- Operations Case、Approval/JIT 已由 Core 在签发边界验证;
SignedWorkforceCommandExecutionGrant@1精确绑定完整 Admin Command Candidate,且 JTI 一次性消费;- Billing-owned
FundingDecision@1精确绑定 Account/Source Revision、Case Ref/Generation/Identity Reservation、Exposure 四元组、Funding Policy Revision、金额、Operation、有效期与严格credits | entitlement{entitlementKey,unit} | money{currency}经济身份,且 JTI 一次性消费; - Metering 为
late_settlement_commandPurpose 签发 Current Receipt; - Billing 在单一 Ledger 事务内原子消费两个 JTI、写 Post-release Adjustment、解决 Exposure、推进 Settlement State、追加严格
LateSettlementCommandResult@1、BillingCaseResolutionApplied@1与 Outbox。
LateSettlementCommandResult@1 固定 Command Basis、Authorization Candidate/Grant、Case Ref/Generation/Identity Reservation、两个已消费 JTI、Funding/Policy、Metering Receipt、Open→Resolved Exposure、Ledger、经济身份与提交后的 Dimension State;同 Command/JTI/Candidate 在响应丢失后返回首次 Result,异 Candidate 拒绝。Result 不反向引用 Applied Fact,Operations 通过 Billing 的定向、版本化 Read Contract 校验它。Operations 消费 Applied Fact 后按 Case Generation与 Exposure Source Revision幂等关闭对应 Case;Case 投影或关闭失败只重试 Outbox 消费,不回滚已经提交的 Ledger。Operations 不拥有 LateSettlementExposure,Billing 也不拥有 Reconciliation Case。
被拒绝的方案
| 方案 | 结论 | 原因 |
|---|---|---|
| Gateway 直接返回可扣费 Usage/Cost | 不采用 | 把供应事实、客户计量、价格和账务混为一体,无法处理更正与双网关一致性 |
| Billing 每次分别查询 Gateway、Metering 与 Execution Current 状态 | 不采用 | 多次查询之间没有统一线性化边界,会形成不可证明的混合快照 |
| 用分布式锁或跨库事务封闭 Attempt 并扣款 | 不采用 | 破坏领域所有权,故障恢复复杂且无法覆盖真实网络分区 |
| 超时或缺 Evidence 时按零 Usage 释放 | 不采用 | 未知状态被伪装成确定事实,可能漏扣或产生双结算 |
| 成功终局后重开 Reservation 重算 | 不采用 | 历史账务不稳定,幂等、审计和客户账单无法成立 |
| Admin 直接改 Ledger/Case/余额 | 不采用 | 绕过领域不变量、审批、资金校验、一次性授权和完整审计 |
实施顺序与门禁
- Contracts:发布全部严格 DTO、判别联合、Canonicalization、Golden Digest、Receipt Purpose、Reason Code 和负向制品;未知字段与未知算法 fail-closed。
- Execution 与 Gateway:实现 Run Manifest 四元组 Read Contract、Attempt Manifest、唯一 Binding/Route、Pre-binding Rejection、不可变 Evidence/Availability、Attempt 关闭 CAS 与验证 Receipt。
- Metering:成为规范 MeterEvent/ProviderCostFact 唯一 Writer,实现 Eligibility、SettlementInputSnapshot、Current Pointer、追加更正和 Purpose-bound Receipt。
- Operations/Admin + Billing恢复入口:先以禁用状态实现两类Reconciliation Case Source Scope、严格Resolution Request/Candidate/Decision、审批/JIT、Signed Command Grant、Billing Operation-first Handler、Applied Fact双Destination消费与全链路Audit;不开放通用数据库修改能力。
- Billing启用:实现 Input Manifest、三组 Validation Bundle、吸收式 Finalization、本地 Ledger 原子事务、Settlement Transition、Late Settlement Exposure、Funding Decision 与 Late Settlement Command Result。只有第4步的Finalization Resolution链已通过跨进程门禁时,才允许任何Producer把Fence推进为
reconciliation_required。 - Canary:只选择一个 Organization、一个文本 Offering、一个固定 Gateway 路径,证明准入、执行、计量、结算、Asset 与诊断完整闭环后再扩展媒体与更多产品。
每一阶段必须覆盖并发 insert-or-compare、CAS 竞争、同 Key 异 Candidate、摘要篡改、Ref 重绑、未知 Schema/算法、重复/乱序/迟到、Receipt 集缺项/夹带、服务提交后响应丢失与事务回滚。不得为了演示跳过前一 Owner 的验证契约,也不得让目标文档字段伪装成已实现能力。
影响
正面影响:
- Text 与 Media Gateway 可以使用同一经济事实协议,同时保留各自执行实现;
- 每次扣款、释放、退款、供应成本和人工补结都能追溯到固定版本与不可变证据;
- 并发与迟到输入有明确归属,不依赖隐式时间窗或“最后写入者获胜”;
- Admin 能处置异常,但不能绕过 Billing、Metering 或 Execution 的领域不变量。
代价:
- Contracts、Repository 约束、Golden 测试与跨服务验证 Receipt 数量明显增加;
- 终局前必须显式封闭 Attempt 和 Charge Dimension,部分请求会处于 Blocked/Reconciliation,而不是过早给出成功账务状态;
- Operations Case 与 Billing Exposure 是两个相互引用但不同 Owner 的对象,需要可靠 Outbox 与幂等消费。
这些复杂度服务于不可重复扣费、不可丢失经济事实和可恢复运维,而不是要求首期部署更多微服务。各模块仍可按当前计划部署在 Core 的明确 Process Role 与 Repository 边界中,直到容量或故障域证据要求新的物理拆分。
当前实施状态
本 ADR 是正式目标决策,不代表运行时已经完成。当前已验收能力仍只到 Organization-backed 受控 Admission、Run、credits Reservation 与事务内 Outbox Append;Gateway Evidence、Attempt Closure、Metering、Finalization、Settlement Transition、Late Settlement 与管理员处置协议都需要按上述门禁实现。PUBLIC_ADMISSION_MODE 在真实端到端 canary 通过前继续保持 disabled。