文档
历史档案文档OceanWay 架构

历史 · 钱包与商业系统

重构前档案,仅供追溯,不作为新版本执行指令

历史档案 · 2026-09-11 重构前快照。 本文中的“当前”“已冻结”和实施顺序属于旧基线。新版本以现行总览实施计划为准。

“共享一个钱包”不等于把所有价值压成一个余额数字。OceanWay 需要统一付款主体、计量与账本,同时清楚区分可消费积分、订阅授权、定向权益、资源包和企业合同。

计费边界

每个个人空间或企业 Organization 只有一个 Billing Account:

Billing Account
├── 积分钱包
│   ├── 充值积分
│   ├── 套餐积分
│   ├── 活动赠送积分
│   └── 企业授信(后续)
├── 定向权益
│   ├── 图片张数
│   ├── 视频次数 / 秒数
│   └── 音频次数
├── 产品订阅与席位
├── 资源包
└── 预算与限额
    ├── Workspace
    ├── Project
    ├── Member
    ├── Product
    ├── Agent
    ├── Service Account
    └── API Key

预算和限额只是消费约束,不是多个子钱包。所有产品最终进入同一个追加式账本,避免余额转移、重复入账和跨产品对账困难。

当前实现基线

现有统一计费 V2 候选已经提供可复用底座:

  • 网页创作与公开 API 共用费率快照、积分账户和账本;
  • 网页通过 Session/内部 Worker 身份执行,公开 API 通过 Developer Key 鉴权;
  • PostgreSQL 使用 NUMERIC(38,18) 与十进制字符串保存精确金额;
  • 支持 Token、分层 Token、按张、按秒、按次和混合计量;
  • 支持预占、结算、释放、退款、待对账和幂等;
  • 套餐、活动、开户和充值积分按批次保存;
  • 图片张数、视频次数/秒数、音频次数等定向权益独立于积分;
  • 一个请求只有在定向权益完整覆盖时才使用权益,否则完整使用积分,不做隐式混扣。

目标架构复用这套精确账本,把当前“按用户所有”升级为“按 Billing Account 所有”,再增加 Organization、Workspace、Project、成员、Service Account 与产品订阅维度。

上述能力是旧系统中的实现候选,不等于新的跨仓链路已经验收。当前正式 Core 基线只完成 credits Reservation;Gateway Evidence 契约、Metering 的规范 Fact Writer、Settlement/Release/Refund 与供应成本对账均仍待实施。

本文中的“对账 / Reconciliation”若未明确写成 Reconciliation Case,只表示 Owner 内核对、Conflict/Blocked 状态或 Incident 诊断,不分配 Case Ref。正式 Operations Case 是封闭联合,只包含 late_settlement_dimension | billing_finalization_run;其他来源必须先完成独立事件、投递、读取、身份、命令和终态 Authority 契约才能加入。

Text/Media Gateway 只保存不可变 ProviderUsageEvidence / ProviderCostEvidence 与其 source ref。每个响应及 Gateway Observation 都必须同时返回 usageEvidenceAvailabilitycostEvidenceAvailability 严格联合;只有各自 available 分支携带对应 Evidence Ref/Schema Version/Digest Algorithm Version/Digest完整四元组,not_reported | pending | unavailable | conflicting 禁止携带任何 Evidence四元字段。网关不创建规范 MeterEvent / ProviderCostFact,也不拥有 OceanWay 用户费率、Rate Card、Reservation、Ledger、Refund、钱包、订阅或用户 Quota。网关内部容量和成本保护不能映射或覆盖用户余额。Media Gateway 也不保留面向客户的价格簿。完整调用边界见调度、执行与模型网关池

价值类型

类型是否进入积分余额是否有期限典型用途
充值积分可长期有效跨平台模型和生成消费
套餐积分随订阅周期套餐内通用用量
活动积分通常有期限拉新与促销
定向生成权益通常有期限指定图片、视频、音频能力
产品授权 / 席位随订阅周期开通漫剧、电商、企业功能
资源包按商品规则存储、并发、专属 Agent 等
企业授信进入可用额度视图合同周期月结或后付费企业

钱包首页应同时展示“可用积分、即将到期权益、订阅、预算与待结算”,不能把它们相加成一个没有业务含义的总数。

统一消费链路

失败边界:

  • 未通过权限、费率或余额检查时不调用上游;
  • 明确未发往上游,或执行失败/取消且不存在符合版本化产品与 Billing Policy 的 canonical MeterEvent 时,也必须等 BillingFinalizationDecision 证明完整 Attempt×Charge Dimension 集合已封闭后,才能释放预占;
  • 产物生成但媒体落盘失败时按原 Reservation 执行补偿或退款;
  • 实际费用超过预占时进入待对账,不允许静默负余额;
  • 重复幂等请求不重复调用、扣款、发放或退款。

产品只提交 Quote 请求;Execution 只提交绑定同一 Attempt 的执行/输出事实与 Gateway Evidence 引用。两者都不能自行铸造规范 Meter Event,也不能直接修改余额。

Media Gateway Task/Provider Attempt Observation 只证明 Provider 侧事实,未来可选的 Callback Receipt 也不例外。Gateway Operational Observation 对 Usage/Cost 必须分别携带必填 Availability 联合,只有 available 分支引用对应 Evidence Ref/Schema Version/Digest Algorithm Version/Digest完整四元组,其他分支禁止全部 Evidence四元字段;它不得携带规范 Fact或请求结算。Metering 只有在绑定同一 Attempt、可信执行/输出与结算资格并验证 Usage Evidence 后,才能追加规范 MeterEvent;失败或取消是否仍具 MeterEvent 资格,由准入时冻结的版本化产品/Billing Policy 决定,不能只看 Run 终态。Billing 依据具结算资格的 canonical MeterEvent 与冻结 Pricing Snapshot 计算目标净额,但只有版本化 BillingFinalizationDecision 证明完整 Attempt×Charge Dimension 集合封闭后,才可原子结算并释放余量;“当前没有合格 MeterEvent”本身不能触发释放。ProviderCostFact 则只要求绑定同一 Attempt 并验证 Provider Cost Evidence,可独立于业务成功、结果取回、Asset 登记和客户 Settlement 追加,以保留“供应商已计费但客户执行失败”的成本事实,但它永远不能单独触发客户扣款;证据缺失或冲突进入供应对账。重复 Evidence、轮询、乱序状态或迟到回调不得重复生成 Fact 或重复结算。

Meter Event 与 Provider Cost Fact

Metering 是规范 MeterEventProviderCostFact 的唯一 Writer。它负责校验 Evidence 引用、将供应侧单位/币种映射为固定版本的规范口径、按稳定幂等键去重,以及用追加式更正链处理 Provider 迟到或修订数据;不得原地覆盖历史 Fact。

每条规范用量事实至少记录:

meterEventId
meterEventSchemaVersion
idempotencyKey
meterEventCandidateDigestAlgorithmVersion = jcs-sha256-v1
meterEventCandidateDigest
tenantKind = organization | personal_space
tenantId
workspaceId
projectId?
billingAccountId
billingReservationId
actorPrincipalId
executionPrincipalId
serviceAccountId?
callerWorkloadPrincipalId?  # 仅服务跳转归因,不是业务 Actor/Execution Principal
product
runId
runStepId
executionAttemptId
agentRevisionId?
modelOfferingRevisionId
modelDeploymentId
chargeDimensionKey
chargeType / chargeTypeRegistryVersion
usage = { quantityDecimal; unit }
usageEvidence = gateway_provider
pricingSnapshotId
billingPolicyRevisionId
settlementEligibility =
  { status=eligible; eligibilityDecisionRef; eligibilityDecisionSchemaVersion }
  | { status=ineligible; eligibilityDecisionRef; eligibilityDecisionSchemaVersion }
normalizationPolicyVersion
supersedesMeterEventId?
correctionReason?
occurredAt / recordedAt
traceId?
correlationId
operationId

usageEvidence 是严格判别联合,不能裸空:

gateway_provider {
  providerUsageEvidenceRef / providerUsageEvidenceSchemaVersion
  providerUsageEvidenceDigestAlgorithmVersion / providerUsageEvidenceDigest
  attemptRouteBindingRef / attemptRouteBindingSchemaVersion
  attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest
  gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion
  gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest
}

首期所有模型执行统一经过 Text/Media Gateway,MeterEvent 只接受 gateway_provider;Provider Usage Evidence、Attempt Route Binding 与 Gateway Route Snapshot 三个内容寻址四元组都必填,并与 Attempt Manifest、Route Binding 和 Route Snapshot 逐层校验,缺失、Pending 或冲突不得生成可结算 MeterEvent。不存在 platform_execution 旁路或“内部任务可信”的隐式 Evidence。未来若确需新增来源,必须先发布独立 Owner、内容寻址 Evidence、定向 Read/Current Validation、Capability Gate 与全链路负测,并升级 Contracts 判别联合;在此之前未知 Kind 一律拒绝。

一个 MeterEvent 只表达一个由 Contracts Registry 管理的 chargeType 和一个 { quantityDecimal, unit },不允许在自由 Map 中混装 Token、张数、秒数等多维用量。quantityDecimal 是 Registry 精度范围内的非负规范十进制字符串:禁止负号、指数形式、NaN/Infinity、超精度和非规范前导形式;unit 必须是该 chargeType + chargeTypeRegistryVersion 注册的单位。Provider 原生 signed adjustment 只保存在 Gateway 的不可变 Source Chain;Gateway Evidence Read Contract 只暴露可验证的非负完整累计 Head。Metering 规范化出的每个 canonical head 同样是非负累计量,账务有符号差额只能由 Billing 比较两个合法 Head 计算。

chargeDimensionKey 由冻结的 Offering Revision 与 Billing Policy 中的计费维度定义解析,是独立于 Registry Version 的稳定维度身份,不能由 Gateway 或客户自报。该不可变 billingPolicyRevisionId 制品必须内含并摘要固定 meteringNormalizationPolicyVersion;MeterEvent 的 normalizationPolicyVersion 只能等于它,迟到首次处理或历史重放禁止读取当前策略。每个 MeterEvent 的 idempotencyKey 固定由 meterEventSchemaVersion + executionAttemptId + chargeDimensionKey + chargeType + chargeTypeRegistryVersion + unit + canonicalUsageEvidenceIdentity + eligibilityDecisionRef + eligibilityDecisionSchemaVersion + normalizationPolicyVersion 的 Contracts 规范摘要确定,不允许实现自行增减字段。canonicalUsageEvidenceIdentity 精确包含 Provider Usage Evidence、Attempt Route Binding、Gateway Route Snapshot 的全部 Ref/Schema Version/Digest Algorithm Version/Digest 与固定 gateway_provider Kind。meterEventCandidateDigest 则以 jcs-sha256-v1 覆盖不含 Repository-owned ID/Time和摘要自身的完整严格 Create Candidate,用于跨服务 SettlementInputSnapshot 验证;它不能替代较窄的唯一业务 Key。meterEventId/recordedAt 由 Repository 首次 insert 生成,occurredAt/traceId/correlationId/operationId/correctionReason 等展示、归因或更正字段不得进入 Key。这样同一 Evidence 在 Eligibility 决策发生合法 Supersession 后会形成新的 Event Head,而不是与旧 Event 同键异 Payload。相同 Key 只比较不含 Repository 生成字段的严格 Create Candidate;业务字段或派生摘要不同写EligibilityProcessingConflictEvidence@1 + MeteringInputWorkAttemptBlocked@1并保持Active Work,完全相同则返回首次 meterEventId/recordedAt,两者都不创建Case。

supersedesMeterEventId 形成单一 Active Head 的直接前驱链。Repository 对 (billingReservationId, executionAttemptId, chargeDimensionKey) 原子限制唯一 Chain Root;首个 Head 冻结 Charge Type/Registry Version/Unit、Normalization Policy 与其余经济身份,后续换 Registry/Policy 不能创建无 Supersedes 的第二 Root,只能形成Owner-local Blocked状态或经未来独立迁移协议处理。整条链必须保持相同的 Tenant、Workspace/Project、Billing Account、Billing Reservation、Actor/Execution Principal、Product、Run/Step/Attempt、Agent Revision(若有)、Offering Revision、Deployment、Charge Dimension Key、Charge Type/Registry Version/Unit、Pricing Snapshot、Billing Policy、Normalization Policy、Correlation、Operation 与 Usage Evidence Kind;只允许规范 Quantity、对应 Evidence Ref/Version、Eligibility Decision Ref/Version、Recorded Time 和规范 Correction Reason 随更正变化。首条不得有 Supersedes;后续必须指向当前直接前驱,重复、第二 Root、分叉、循环、跨 Reservation/Attempt/经济维度/策略/价格的替代全部隔离并绑定处理冲突证据,不得创建Case。supersedesMeterEventId 是 Metering Repository 内部强外键,前驱行自身保存 meterEventSchemaVersion,因此不另设可漂移的外部 Schema Version 字段;跨服务只传当前 MeterEvent Ref/Version。

Billing 对 (billingReservationId, executionAttemptId, chargeDimensionKey) 终身只允许一笔 Base Settlement,并校验该 Root 冻结的 chargeType + chargeTypeRegistryVersion + unit。Reservation 仍为 active 时,MeterEvent/Eligibility Head 只积累事实,不写客户 Ledger;首次 Base 只能在 BillingFinalizationDecision 的锁定事务中按该维度 Current Head 创建。终局后的合法 Current Head 更正才以新 MeterEvent ID 幂等追加 Adjustment/Refund;终局后首次出现 Eligible 则按下文 Late Settlement 创建正式维度级Case,绝不补建第二笔 Base。负数、指数、超精度、Registry/Unit 错配、第二 Root 与跨维度替代只形成Metering Owner-local Conflict/Blocked并阻止可结算Fact,不直接创建Case。

Billing 为每个维度维护唯一、可锁定的 BillingSettlementDimensionState。所有客户账务对象复用同一个严格经济身份,Usage 的 chargeType/unit 只说明“用了什么”,不能代替“从哪一种价值结算”:

SettlementEconomicIdentity@1 =
  { kind=credits }
  | { kind=entitlement; entitlementKey; unit }
  | { kind=money; currency }

BillingSettlementInputPointer@1 = {
  settlementInputSnapshotRef / settlementInputSnapshotSchemaVersion;
  settlementInputSnapshotDigestAlgorithmVersion / settlementInputSnapshotDigest / settlementInputStateVersion;
  eligibilityDecisionRef / eligibilityDecisionSchemaVersion;
  meterEventHead={state=none}|{state=available; meterEventRef; meterEventSchemaVersion};
  settlementEconomicIdentity=SettlementEconomicIdentity@1;
  targetAmountDecimal
}

BillingSettlementInputConsumption@1 = {
  settlementInputConsumptionRef / settlementInputConsumptionSchemaVersion
  settlementInputConsumptionDigestAlgorithmVersion = jcs-sha256-v1
  settlementInputConsumptionDigest
  billingReservationId / billingAccountId / runId / runStepId / executionAttemptId / chargeDimensionKey
  settlementEconomicIdentity = SettlementEconomicIdentity@1
  evaluatedInput = BillingSettlementInputPointer@1
  evaluationSource =
    { kind=finalization; billingFinalizationOperationId;
      finalizationInputManifestRef / finalizationInputManifestSchemaVersion;
      finalizationInputManifestDigestAlgorithmVersion / finalizationInputManifestDigest }
    | { kind=post_finalization_transition; billingTransitionOperationId;
        billingTransitionValidationOperationId;
        transitionBasisDigestAlgorithmVersion / transitionBasisDigest }
  fromStateRevision / toStateRevision
  recordedAt
}

三个分支严格互斥:Credits 禁止 Entitlement/Money 字段;Entitlement 必须完整携带不可变 entitlementKey + unit;Money 必须携带规范 Currency。数额始终放在相邻的 Amount/Target/Delta Decimal 字段中,不把 Currency 或 Entitlement Unit 压成一个模糊 settlementUnit。该身份必须与 Admission 的严格 BillingValue、Reservation 消费来源与冻结 Pricing/Billing Policy逐项可解析且相等;不能把 Credits、不同 Entitlement Bucket/Unit 或不同 Currency 相加、替代或重放。

Consumption Digest覆盖除 Repository-owned Ref/Time和摘要自身外的完整严格 Candidate;evaluationSource两分支互斥,Finalization初始化固定 fromStateRevision=0 + toStateRevision=1,后续 Transition固定 to=from+1。Repository对 (billingReservationId, executionAttemptId, chargeDimensionKey, settlementInputSnapshotRef, settlementInputSnapshotSchemaVersion, settlementInputSnapshotDigestAlgorithmVersion, settlementInputSnapshotDigest, settlementInputStateVersion)设置终身唯一约束,并以强外键确保 evaluatedInput与该 Snapshot、Eligibility/MeterEvent Head及经济身份相等。相同输入只允许一条 Consumption;若并发 insert冲突,失败方必须重读并完整比较后返回 already_evaluated,不能仅凭唯一键错误假定成功,也不能换 Operation再创建新 Revision。

BillingSettlementDimensionState@1 = {
  billingReservationId / billingAccountId / runId / runStepId / executionAttemptId / chargeDimensionKey
  chargeType / chargeTypeRegistryVersion / unit
  settlementEconomicIdentity = SettlementEconomicIdentity@1
  baseSettlementId?
  lastEvaluatedInput = BillingSettlementInputPointer@1
  lastInputConsumptionRef / lastInputConsumptionSchemaVersion
  lastInputConsumptionDigestAlgorithmVersion / lastInputConsumptionDigest
  settledInput = BillingSettlementInputPointer@1
  lateSettlementCaseLink =
    { state=none; nextCaseGeneration=1 }
    | { state=opening; caseGeneration; lateSettlementExposureRef / lateSettlementExposureSchemaVersion;
        lateSettlementExposureDigestAlgorithmVersion / lateSettlementExposureDigest;
        exposureRevision; caseRequestOperationId; reconciliationCaseRef;
        reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion;
        reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest }
    | { state=open; caseGeneration; lateSettlementExposureRef / lateSettlementExposureSchemaVersion;
        lateSettlementExposureDigestAlgorithmVersion / lateSettlementExposureDigest;
        exposureRevision; caseRequestOperationId; reconciliationCaseRef;
        reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion;
        reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest }
    | { state=resolving; caseGeneration; lateSettlementExposureRef / lateSettlementExposureSchemaVersion;
        lateSettlementExposureDigestAlgorithmVersion / lateSettlementExposureDigest;
        exposureRevision; caseRequestOperationId; reconciliationCaseRef;
        reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion;
        reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest;
        billingCaseResolutionAppliedRef / billingCaseResolutionAppliedSchemaVersion;
        billingCaseResolutionAppliedDigestAlgorithmVersion / billingCaseResolutionAppliedDigest }
  stateRevision
}

只有成功 Finalization 事务才为完整 Manifest 中每个维度初始化该状态,并为每个维度原子追加一条 evaluationSource.kind=finalizationBillingSettlementInputConsumption@1;State 的 lastInputConsumption 四元组必须指向它。顶层、Consumption、lastEvaluatedInputsettledInput、Base Settlement 与 Ledger 必须携带并逐项比较同一 SettlementEconomicIdentity@1lastEvaluatedInput 表示 Billing 已消费到的资格/计量链位置,settledInput 表示客户账本已经实现的目标净额,二者不得合并:Eligible 维度原子创建唯一 Base 并让两者指向同一输入;Usage 已 available 但资格 Ineligible 时不写 Base,却必须让两者保存该 Ineligible Decision、其实际 Current MeterEvent Head 与零目标额;gateway_provider_terminal_none | execution_not_dispatched 这两类没有 MeterEvent 的 Ineligible 维度保存对应 Decision、严格 meterEventHead.state=none 与零目标额。这样 M1(Ineligible)→M2(Eligible) 沿真实 MeterEvent 链前进,Gateway Terminal-none→首次 Eligible 则从已处理的 Eligibility Head 前进;Not-dispatched 因事实不可修订,不能在同一 Attempt 上转为 Eligible。

Settlement Input 必达唤醒与 Catch-up Fence

“Receipt之后的新Snapshot交给后续Transition”必须有耐久收敛协议,不能假设未来还会再来一条通知。Metering每次原子追加SettlementInputSnapshot@1并推进Current Pointer时,必须在同一Metering事务追加Contracts注册的metering.settlement-input.current-changed@1 Event、冻结Delivery Set并创建billing_settlement_catch_up Mandatory Delivery;Snapshot、Current Pointer或Outbox任一失败全部回滚。Event Payload是封闭DTO:

SettlementInputCurrentChanged@1 = {
  tenantKind / tenantId / workspaceId / projectId?
  billingReservationId / billingAccountId / runId / runStepId
  executionAttemptId / chargeDimensionKey
  settlementEconomicIdentity = SettlementEconomicIdentity@1
  currentSettlementInputSnapshotRef / currentSettlementInputSnapshotSchemaVersion
  currentSettlementInputSnapshotDigestAlgorithmVersion / currentSettlementInputSnapshotDigest
  currentSettlementInputStateVersion
  supersedesSettlementInputSnapshot =
    { state=root }
    | { state=supersedes; ref; schemaVersion; digestAlgorithmVersion; digest; stateVersion }
}

Event的aggregateId是Contracts按billingReservationId + runStepId + executionAttemptId + chargeDimensionKey确定性派生的Settlement Dimension ID,aggregateRevision=currentSettlementInputStateVersion。Payload、Snapshot正文和Current Pointer必须逐项相等;同一Snapshot事务只能有一个同摘要Event。未知Schema/算法、同Revision异Digest、跨经济身份或前驱错误都拒绝,不能发一条仅含“维度已变化”的弱通知。

Billing Consumer收到Event后,在单一Billing Inbox事务中按相同维度键锁定并单调更新SettlementInputWakeupWatermark:保存最高已接受Snapshot完整五元组、Event Envelope完整摘要和inboxRevision。成功Finalization之前,它必须同步推进自Reservation创建起就存在的BillingFinalizationFence:普通等待/评估态形成新的Trigger Snapshot/Work或与当前Pending Generation合并;若Fence已是reconciliation_required,则只以严格held_for_resolution Consumer Receipt耐久推进Watermark,不生成Work、不离开当前Case,待该代Resolution Applied Event把最新Watermark纳入新Trigger。Finalization已提交后,则把对应Dimension Catch-up Fence推进为pending并insert-or-compare Durable Work。只有Watermark、所选Fence动作与版本化Consumer Receipt一起提交后才Ack Mandatory Delivery;不得因Dimension State尚不存在而停在无记录的awaiting_finalization、丢弃、终结或只写日志。若预期Finalization Fence缺失,不写Receipt/Ack并返回retryable,由完整性告警阻断终局。相同Event同摘要幂等Ack,较旧合法Revision记录为already_observed,同Revision异摘要或无法证明的前驱进入Quarantine且不伪造已追平。这里的Ack只表示“唤醒输入已耐久接收或在当前Case下等待Resolution”,不表示Billing已经消费该Snapshot。

成功Finalization事务必须锁定或初始化每个维度的Watermark,并与Dimension State、Finalization Consumption、Ledger/Release、Committed Fact和Outbox一起创建唯一BillingSettlementCatchUpFence@1及首个Durable Work Item;即使当前Watermark不领先,也必须以pending_verification启动一次Owner Current验证,防止事件尚未投递。Fence至少保存维度身份与经济身份、lastEvaluatedInput完整五元组、highestNotifiedInput完整五元组或none、fenceRevisioncoordinationState=pending_verification|pending|caught_up|blocked_on_owner_conflict,以及最近一次成功billing_catch_up_fence Receipt四元组或none。Blocked态保留同一Active Work并绑定BillingSettlementCatchUpWorkAttemptBlocked@1,不是Operations Case终态。事务末Deferred Constraint必须证明Manifest每个维度恰有一个Fence和可恢复Work;缺失任一项则Finalization整体回滚。

上述Watermark、Fence与Work使用以下封闭Contract,而不是实现私有的可变Job JSON:

SettlementInputWakeupWatermark@1 = {
  settlementInputWakeupWatermarkId / settlementInputWakeupWatermarkSchemaVersion
  billingReservationId / billingAccountId / runId / runStepId
  executionAttemptId / chargeDimensionKey
  settlementEconomicIdentity = SettlementEconomicIdentity@1
  inboxRevision
  highestNotification =
    { state=none }
    | { state=observed;
        sourceEventId / sourceEventType / sourceEventSchemaVersion;
        sourceEventEnvelopeDigestAlgorithmVersion / sourceEventEnvelopeSha256;
        settlementInput = BillingSettlementInputPointer@1 }
}

BillingSettlementCatchUpFence@1 = {
  billingSettlementCatchUpFenceId / billingSettlementCatchUpFenceSchemaVersion
  billingReservationId / billingAccountId / runId / runStepId
  executionAttemptId / chargeDimensionKey
  settlementEconomicIdentity = SettlementEconomicIdentity@1
  fenceRevision / latestWorkGeneration
  lastEvaluatedInput = BillingSettlementInputPointer@1
  highestNotifiedInput =
    { state=none }
    | { state=observed;
        sourceEventId / sourceEventType / sourceEventSchemaVersion;
        sourceEventEnvelopeDigestAlgorithmVersion / sourceEventEnvelopeSha256;
        settlementInput = BillingSettlementInputPointer@1 }
  coordinationState =
    { state=pending_verification;
      activeWork={ state=pending; workGeneration;
        billingSettlementCatchUpWorkRef / billingSettlementCatchUpWorkSchemaVersion;
        billingSettlementCatchUpWorkDigestAlgorithmVersion / billingSettlementCatchUpWorkDigest;
        billingCatchUpValidationOperationId } }
    | { state=pending;
        activeWork={ state=pending; workGeneration;
          billingSettlementCatchUpWorkRef / billingSettlementCatchUpWorkSchemaVersion;
          billingSettlementCatchUpWorkDigestAlgorithmVersion / billingSettlementCatchUpWorkDigest;
          billingCatchUpValidationOperationId } }
    | { state=caught_up; activeWork={ state=none };
        currentValidationReceiptRef / currentValidationReceiptSchemaVersion;
        currentValidationReceiptDigestAlgorithmVersion / currentValidationReceiptDigest }
    | { state=blocked_on_owner_conflict;
        activeWork={ state=pending; workGeneration;
          billingSettlementCatchUpWorkRef / billingSettlementCatchUpWorkSchemaVersion;
          billingSettlementCatchUpWorkDigestAlgorithmVersion / billingSettlementCatchUpWorkDigest;
          billingCatchUpValidationOperationId };
        billingSettlementCatchUpWorkAttemptBlockedRef / billingSettlementCatchUpWorkAttemptBlockedSchemaVersion;
        billingSettlementCatchUpWorkAttemptBlockedDigestAlgorithmVersion / billingSettlementCatchUpWorkAttemptBlockedDigest }
}

BillingCatchUpBasisCandidate@1 = {
  billingCatchUpBasisSchemaVersion
  billingCatchUpBasisDigestAlgorithmVersion = jcs-sha256-v1
  billingCatchUpBasisDigest
  billingReservationId / billingAccountId / runId / runStepId
  executionAttemptId / chargeDimensionKey
  settlementEconomicIdentity = SettlementEconomicIdentity@1
  billingSettlementCatchUpFenceId / fenceRevision / workGeneration
  lastEvaluatedInput = BillingSettlementInputPointer@1
  highestNotifiedInput = BillingSettlementCatchUpFence@1.highestNotifiedInput
  settlementInputToValidate = BillingSettlementInputPointer@1
}

BillingSettlementCatchUpWork@1 = {
  billingSettlementCatchUpWorkRef / billingSettlementCatchUpWorkSchemaVersion
  billingSettlementCatchUpWorkDigestAlgorithmVersion = jcs-sha256-v1
  billingSettlementCatchUpWorkDigest
  billingReservationId / billingAccountId / runId / runStepId
  executionAttemptId / chargeDimensionKey
  billingSettlementCatchUpFenceId / fenceRevision / workGeneration
  billingCatchUpBasisSchemaVersion
  billingCatchUpBasisDigestAlgorithmVersion / billingCatchUpBasisDigest
  billingCatchUpValidationOperationId
  createdAt
}

BillingSettlementCatchUpWorkAttemptStarted@1 = {
  billingSettlementCatchUpWorkAttemptStartedRef / billingSettlementCatchUpWorkAttemptStartedSchemaVersion
  billingSettlementCatchUpWorkAttemptStartedDigestAlgorithmVersion = jcs-sha256-v1
  billingSettlementCatchUpWorkAttemptStartedDigest
  billingSettlementCatchUpWorkRef / billingSettlementCatchUpWorkSchemaVersion
  billingSettlementCatchUpWorkDigestAlgorithmVersion / billingSettlementCatchUpWorkDigest
  billingCatchUpValidationOperationId
  billingReservationId / billingAccountId / runId / runStepId
  executionAttemptId / chargeDimensionKey / workGeneration
  attemptNumber / fencingToken / leaseExpiresAt / startedAt
}

BillingSettlementCatchUpWorkAttemptBlocked@1 = {
  billingSettlementCatchUpWorkAttemptBlockedRef / billingSettlementCatchUpWorkAttemptBlockedSchemaVersion
  billingSettlementCatchUpWorkAttemptBlockedDigestAlgorithmVersion = jcs-sha256-v1
  billingSettlementCatchUpWorkAttemptBlockedDigest
  billingSettlementCatchUpWorkRef / billingSettlementCatchUpWorkSchemaVersion
  billingSettlementCatchUpWorkDigestAlgorithmVersion / billingSettlementCatchUpWorkDigest
  billingCatchUpValidationOperationId
  billingReservationId / billingAccountId / runId / runStepId
  executionAttemptId / chargeDimensionKey / workGeneration
  attemptNumber / fencingToken
  blockingCause =
    { kind=settlement_input_owner_conflict;
      settlementInputConflictFactRef / settlementInputConflictFactSchemaVersion;
      settlementInputConflictFactDigestAlgorithmVersion / settlementInputConflictFactDigest }
  blockedAt
}

BillingSettlementCatchUpWorkAttemptFinished@1 = {
  billingSettlementCatchUpWorkAttemptFinishedRef / billingSettlementCatchUpWorkAttemptFinishedSchemaVersion
  billingSettlementCatchUpWorkAttemptFinishedDigestAlgorithmVersion = jcs-sha256-v1
  billingSettlementCatchUpWorkAttemptFinishedDigest
  billingSettlementCatchUpWorkRef / billingSettlementCatchUpWorkSchemaVersion
  billingSettlementCatchUpWorkDigestAlgorithmVersion / billingSettlementCatchUpWorkDigest
  billingCatchUpValidationOperationId
  billingReservationId / billingAccountId / runId / runStepId
  executionAttemptId / chargeDimensionKey / workGeneration
  attemptNumber / fencingToken
  outcome =
    { state=caught_up;
      currentValidationReceiptRef / currentValidationReceiptSchemaVersion;
      currentValidationReceiptDigestAlgorithmVersion / currentValidationReceiptDigest }
    | { state=transition_applied;
        billingSettlementTransitionRef / billingSettlementTransitionSchemaVersion;
        billingSettlementTransitionDigestAlgorithmVersion / billingSettlementTransitionDigest;
        billingSettlementInputConsumptionRef / billingSettlementInputConsumptionSchemaVersion;
        billingSettlementInputConsumptionDigestAlgorithmVersion / billingSettlementInputConsumptionDigest;
        resultingLastEvaluatedInput=BillingSettlementInputPointer@1;
        successorWorkGeneration;
        successorBillingSettlementCatchUpWorkRef / successorBillingSettlementCatchUpWorkSchemaVersion;
        successorBillingSettlementCatchUpWorkDigestAlgorithmVersion / successorBillingSettlementCatchUpWorkDigest;
        successorBillingCatchUpValidationOperationId }
    | { state=already_evaluated;
        billingSettlementInputConsumptionRef / billingSettlementInputConsumptionSchemaVersion;
        billingSettlementInputConsumptionDigestAlgorithmVersion / billingSettlementInputConsumptionDigest;
        resultingLastEvaluatedInput=BillingSettlementInputPointer@1;
        successorWorkGeneration;
        successorBillingSettlementCatchUpWorkRef / successorBillingSettlementCatchUpWorkSchemaVersion;
        successorBillingSettlementCatchUpWorkDigestAlgorithmVersion / successorBillingSettlementCatchUpWorkDigest;
        successorBillingCatchUpValidationOperationId }
    | { state=superseded; supersededByWorkGeneration }
  finishedAt
}

readBillingSettlementCatchUpWorkByOperation(
  billingCatchUpValidationOperationId,
  expected billingReservationId, billingAccountId, runId, runStepId,
  executionAttemptId, chargeDimensionKey
)
  -> { result=found;
       work=BillingSettlementCatchUpWork@1;
       basis=BillingCatchUpBasisCandidate@1 }
   | { result=not_found }
   | { result=conflicting; conflictManifestRef / conflictManifestSchemaVersion;
       conflictManifestDigestAlgorithmVersion / conflictManifestDigest }

Watermark的highestNotification与Fence的highestNotifiedInput必须逐项一致且只接受同一Settlement Dimension Aggregate的连续或可验证单调后继;同Revision异摘要、跨经济身份或前驱缺口进入Quarantine。Basis摘要覆盖除摘要自身外的全部严格字段;Work摘要覆盖除Repository-owned Ref/Time和摘要自身外的完整Candidate。billingCatchUpValidationOperationId按Contracts固定Namespace由purpose=billing_catch_up_fence + Basis带算法摘要 + settlementInputToValidate完整五元组确定性派生并进入Work摘要,不能由Worker随机生成。Repository对(billingReservationId, runStepId, executionAttemptId, chargeDimensionKey, workGeneration)和Operation分别唯一且insert-or-compare;Fence为pending_verification|pending|blocked_on_owner_conflict时必须且只能绑定latestWorkGeneration对应的唯一Work,caught_up必须没有Active Work。blocked_on_owner_conflict还必须绑定同Work/Operation/Generation的Blocked Fact与Metering Owner Conflict Fact完整四元组,不创建Operations Case。数据库Check/Deferred Constraint和Contracts负向Golden拒绝Pending无Work、Caught-up夹带Work、Blocked无Work/无Fact、Basis/Fence/Watermark错配及同代异摘要。

领取事务锁定Fence/Work后递增Attempt Number和该维度Fencing Token并追加Started Fact;完成事务必须以相同Token重新锁定Fence、确认Generation/Revision/Basis仍是Current,并原子追加Finished Fact及对应Receipt或Transition/Consumption。Owner Read返回严格Conflict Fact时则以相同Token写Blocked Fact、释放本次Lease并保留原Active Work/Operation/Generation,不写Finished、不把Fence伪装成处理完成、不创建Case。Owner修复后由配置的唤醒策略重领同Work;更新Current Event到达则原子supersede旧Work并建立新Generation。caught_up完成后Fence进入同名无Work状态;transition_applied|already_evaluated不能直接声称caught up,它们必须在同一事务把Fence的lastEvaluatedInput重基线为Consumption所证明的resultingLastEvaluatedInput,递增Fence Revision/Generation,基于新基线构造新的Basis/Operation/内容寻址Work,并让Fence保持pending_verification且Active Work与Finish中的Successor字段全等。旧Lease或旧Generation只能写/返回superseded,不得推进Dimension State、Ledger或Fence。Worker在领取前、Started后、Owner Read后、Transition提交后或响应返回前崩溃,都由未完成Started扫描或Operation Read恢复同一Work/Basis/Operation;租约和重试策略由Operations Policy配置,不写死固定次数。Read只接受精确Billing Catch-up Worker Audience与完整维度Scope,重算Basis/Work摘要和Operation,不提供latest、列表或跨维度读取。

Catch-up Worker先以当前Billing lastEvaluatedInput、本地Watermark和Fence Revision构造不含Receipt的BillingCatchUpBasisCandidate,再按Contracts固定Namespace由purpose=billing_catch_up_fence + Basis带算法摘要 + 待验证SettlementInput完整五元组确定性派生billingCatchUpValidationOperationId。它调用validateSettlementInputCurrent验证lastEvaluatedInput

  • 返回not_current时,使用响应给出的Current五元组执行精确readSettlementInput、验证从lastEvaluatedInput到Current的完整Lineage,并转入正常post_finalization_transition;Catch-up Receipt不能代替Transition Receipt或驱动Ledger。Transition应用或发现同Input已合法消费后,必须按上文在同一Billing事务重基线Fence并创建下一代pending_verification Work,不能清空Work后停留在Pending,也不能拿Transition Receipt冒充Catch-up Current Receipt。
  • 返回current时,才在Billing本地事务锁定Dimension State、Watermark和Fence,重新比较lastEvaluatedInput、Fence Revision及highestNotifiedInput <= validatedCurrent,保存Receipt并把Fence标记caught_up
  • Watermark在本地提交前已经推进时,本次Receipt只作未采用审计证据,Fence保持pending;Watermark在提交后推进时,Event Consumer会以更高Revision重新打开Fence并创建Work。

因此S1 Receipt → S2 Snapshot/Event先于Finalization到达并被Ack → Finalization按S1提交 → 此后没有S3仍会由早到Watermark和Finalization自建Work追平S2;第一代看到not_current(S2)并应用Transition,原子创建的第二代验证新基线S2为Current后才进入caught_up,不需要等待不存在的S3。若S2发生在Catch-up Receipt之后,Mandatory Event最终会重新唤醒;事件长期未投递会保持Mandatory Delivery non-terminal并触发告警,而不会被“caught_up”掩盖。Retention不得删除仍被Watermark、Fence、Work、Receipt、Consumption或Transition引用的Event/Snapshot。Contracts与跨进程测试必须覆盖通知先于/晚于Finalization、通知Ack后Worker崩溃、Finalization提交后首个Work丢失恢复、Transition提交与Successor Work任一步失败全事务回滚、Transition响应丢失后恢复同一Successor、Receipt与新Snapshot竞态、S2→S3合并追平、同Revision异Digest、跨维度/经济身份重放,以及上述“无S3”反例。

终局后的处理请求必须携带 Current Settlement Input完整四元组/State Version,以及由 Contracts固定 Namespace按 (billingReservationId, billingAccountId, runId, runStepId, executionAttemptId, chargeDimensionKey, SettlementInputSnapshot完整四元组, stateVersion) 确定性派生的逻辑 billingTransitionOperationId;Worker不能自选随机 Operation。Billing认证调用方、严格解码并验证派生值后,先以该逻辑 Operation与这组稳定输入身份调用下述 Operation Result Read,早于当前 Receipt、State、时间或重新构造 Basis;若已有 Transition且 Account/Run/Step/Dimension/Input/经济身份相等,读取返回完整 Fact后重算其 Basis与最终摘要,直接返回首次 applied结果,即使原 Basis只存在于已被后续 State Revision超越的 Fact中;同 Operation绑定其他输入或正文冲突。只有 not_found 才读取 Current SettlementInputSnapshot与当前 Dimension State,构造不含 Receipt的完整 Transition Basis;Basis摘要确定后,再按 (billingTransitionOperationId, billingReservationId, runStepId, executionAttemptId, chargeDimensionKey, fromStateRevision, basisDigestAlgorithmVersion, basisDigest) 确定性派生本轮 billingTransitionValidationOperationId,以该验证尝试 Operation调用 purpose=post_finalization_transition取得 Metering Current Receipt并继续锁定事务。这样提交后立刻崩溃的新 Worker无需预先保存旧 Basis也能恢复原结果,而并发前驱推进导致 CAS失败时可以基于新 State重建新 Basis与新 Validation Operation,不会让未采用的旧 Receipt永久毒化同一个逻辑请求。

锁定后按 (billingReservationId, runStepId, executionAttemptId, chargeDimensionKey, SettlementInputSnapshot完整四元组, stateVersion) 查询唯一 Consumption Fence。若已有记录且其 evaluatedInput与经济身份相等,Billing必须验证该 Consumption通过 Finalization Committed Fact或引用它的 Transition处于该 Dimension已提交、无分叉的 Revision祖先链,再返回 already_evaluated;它可以等于当前 lastInputConsumption,也可以已被合法后继超越,不能因为 Current State前进就误报冲突。响应携带原 Consumption四元组、其 evaluationSource、可用的原 Transition四元组及观察到的 Current State Revision,且不得追加 Transition、Exposure、Ledger、Case Request、Outbox或任何 State Revision。同一输入绑定不同 Pointer/身份、Consumption不在已提交链或链分叉才进入 Reconciliation。只有 Consumption不存在、且当前Snapshot能通过完整、无缺口、无分叉的 Snapshot/Eligibility/MeterEvent Supersession路径证明为 lastEvaluatedInput的严格后继时才能继续;不要求Billing逐条提交已被更高累计Snapshot超越的每个中间版本。Settlement Input Event只负责耐久推进Watermark和唤醒上述Catch-up Fence,Worker仍以Owner Current验证结果为准:若Billing停在S1而Metering已连续推进S2→S3,则以S3作为本轮逻辑输入,按Owner Read逐条验证并把有序settlementInputLineage=[S2,S3]完整五元组固化进Transition Basis,只对Current S3取得线性化Receipt,再以S3累计Target计算净Delta并提交一次Consumption/Transition。MeterEvent为available时,Lineage每一步都必须是上一MeterEvent/Eligibility Head的合法直接后继;上一状态为none时只允许在Eligibility路径已前进后接收该维度唯一Root。任一中间Snapshot不可读、前驱五元组不等、State Version不连续、分叉或S3在验证时已不再Current都必须重读或进入Reconciliation,不能跳链猜测;事件Watermark不能替代Owner正文、Lineage或Current Receipt。

处理器按已验证 Snapshot重新计算 Target,并以 Current Target − settledInput.targetAmountDecimal 得到 Delta:delta < 0时在同一 Billing事务追加 Consumption、不可变 BillingSettlementTransition@1与 Ledger Refund/负 Adjustment,把 lastEvaluatedInput + lastInputConsumption + settledInput一起推进;只有 Case Link为 opening | open | resolving时才同时追加 Resolved Exposure与 BillingCaseResolutionApplied@1并保持/推进 resolving,Link为 none时固定 caseAction=none且禁止伪造 Case/Exposure/Applied Fact。delta = 0不写 Ledger,但以同一 Consumption/Transition/CAS推进三个 Pointer;同样只有存在上述 Case Link时才追加 Resolved Exposure/Applied Fact,Link为 nonecaseAction=nonedelta > 0只在 Billing内追加 Consumption、幂等创建或替换唯一 LateSettlementExposure@1,把 lastEvaluatedInput + lastInputConsumption推进到 Current、settledInput保持不动,并通过 Outbox请求 Operations创建或更新已预分配 Ref的 Reconciliation Case。Case业务记录回执到达前状态为 opening,但该状态已经携带稳定 Case Ref;不得审批或执行扣款。

Operations 是 Reconciliation Case 唯一 Owner;Billing 在需要首次打开一个 Late Settlement Case 时,必须先调用下述无业务 Case 副作用的身份预留契约,取得稳定 Case Ref 后才能构造 Transition Basis。身份预留可以留下未使用记录,但不能创建可见 Case、触发告警或审批:

ReconciliationCaseIdentityReservation@1 = {
  reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion
  reconciliationCaseIdentityReservationDigestAlgorithmVersion = jcs-sha256-v1
  reconciliationCaseIdentityReservationDigest
  reconciliationCaseRef
  sourceOwner=billing / sourceKind=late_settlement
  billingReservationId / billingAccountId / runId / runStepId / executionAttemptId / chargeDimensionKey
  caseGeneration / caseRequestOperationId
  predecessorCase =
    { state=root }
    | { state=supersedes; previousCaseGeneration; previousReconciliationCaseRef;
        previousIdentityReservationRef / previousIdentityReservationSchemaVersion;
        previousIdentityReservationDigestAlgorithmVersion / previousIdentityReservationDigest }
  reservedAt
}

reserveReconciliationCaseIdentity(
  sourceOwner=billing, sourceKind=late_settlement,
  billingReservationId, billingAccountId, runId, runStepId, executionAttemptId, chargeDimensionKey,
  caseGeneration, caseRequestOperationId, predecessorCase
)
  -> { result=reserved; reservation=ReconciliationCaseIdentityReservation@1 }
   | { result=conflicting; conflictReservationRef / conflictReservationSchemaVersion;
       conflictReservationDigestAlgorithmVersion / conflictReservationDigest }

readReconciliationCaseIdentityReservationBySource(
  sourceOwner=billing, sourceKind=late_settlement,
  billingReservationId, billingAccountId, runId, runStepId, executionAttemptId, chargeDimensionKey,
  caseGeneration, expected caseRequestOperationId, predecessorCase
)
  -> { result=found; reservation=ReconciliationCaseIdentityReservation@1 }
   | { result=not_found }
   | { result=conflicting; conflictReservationRef / conflictReservationSchemaVersion;
       conflictReservationDigestAlgorithmVersion / conflictReservationDigest }

readReconciliationCaseByReservedIdentity(
  ReconciliationCaseIdentityReservation完整四元组,
  expected reconciliationCaseRef, billingReservationId, billingAccountId, runId, runStepId, executionAttemptId,
  chargeDimensionKey, caseGeneration, caseRequestOperationId
)
  -> { result=not_created }
   | { result=found; reconciliationCaseRef; caseRevision;
       sourceExposureRef / sourceExposureSchemaVersion;
       sourceExposureDigestAlgorithmVersion / sourceExposureDigest / sourceExposureRevision;
       caseState }
   | { result=conflicting; conflictRef }

Reservation摘要覆盖除 Repository-owned Reservation Ref/Time和摘要自身外的严格字段,并包含已分配的 reconciliationCaseRef、Case Generation与严格 Predecessor联合。caseRequestOperationId不是 Worker随机值,而是 Contracts固定 Namespace下由 (sourceOwner, sourceKind, billingReservationId, billingAccountId, runId, runStepId, executionAttemptId, chargeDimensionKey, caseGeneration)确定性派生的稳定 ID;所有 Worker与重试必须得到相同值,调用方不能覆盖。Operations对同一组 Source/Generation字段唯一,并要求同维度第一代固定为 1、后继固定为前代+1且 Predecessor完整四元组相等;同 Generation请求返回同一 Reservation/Case Ref,异候选冲突。若 Operations已提交预留但 Billing在保存响应或 Transition前崩溃,新 Worker必须用相同确定性 Operation调用 readReconciliationCaseIdentityReservationBySource或重放 reserve,取得并严格验证原 Reservation完整四元组后采用,不能让孤儿预留永久封锁 Generation,也不能生成第二个 Ref。Source Read只接受精确 Billing Case Identity Workload Audience/Scope,必须严格验证 Source Key、Operation、Predecessor、摘要与 Repository绑定,不提供列表或模糊查询。

lateSettlementCaseLink=opening|open下的更高正 Exposure沿用同一 Generation/Reservation/Case Ref并更新 Source Revision;一旦 Link=resolving,之后再次出现正差额必须预留 caseGeneration+1的新 Reservation与 Case Ref,旧 Approval/JIT/Grant不得迁移、复活或授权新暴露。Billing的 Case Request Outbox必须携带 Generation、Case Ref、Reservation完整四元组和 Exposure完整四元组/Revision;Operations先验证 Reservation,再按预分配 Ref与单调 Source Revision幂等创建或更新 Case,并把 Source Identity原样冻结。回执中的 Case Revision只用于可观测,不进入 Billing后续解决授权或异步 CAS;Billing只在 State仍为相同 Generation/Reservation/Case Ref/Exposure Revision的 opening时推进 open,任何旧代或旧 Revision create ack都不得把 resolving或新一代 Link恢复为 open。若 create ack丢失,Billing使用严格 Read Contract收敛,而不是另建同代 Case。Case等待期间可以因评论、指派、审批或 Incident关联合法增加自己的 Revision,不能让这些本地变化永久毒化随后到达的 Billing解决事实。Contracts/Operations/Billing恢复测试必须覆盖 reserve提交→响应或Billing预提交前崩溃→新Worker处理同一或更新Snapshot,并证明它读取/采用原 Reservation、只出现一个 Case Ref且 Generation继续可用。

BillingCaseResolutionApplied@1 是 Billing-owned 的严格追加事实:

billingCaseResolutionAppliedRef / billingCaseResolutionAppliedSchemaVersion
billingCaseResolutionAppliedDigestAlgorithmVersion = jcs-sha256-v1
billingCaseResolutionAppliedDigest
billingCaseResolutionAppliedIdempotencyKey
reconciliationCaseRef / caseGeneration
reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion
reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest
billingReservationId / billingAccountId
runId / runStepId / executionAttemptId / chargeDimensionKey
settlementEconomicIdentity = SettlementEconomicIdentity@1
resolvedExposureRef / resolvedExposureSchemaVersion
resolvedExposureDigestAlgorithmVersion / resolvedExposureDigest / resolvedExposureRevision
resolutionOperationId
resolutionSource =
  { kind=settlement_transition;
    billingSettlementTransitionRef / billingSettlementTransitionSchemaVersion;
    billingSettlementTransitionDigestAlgorithmVersion / billingSettlementTransitionDigest }
  | { kind=late_settlement_command;
    lateSettlementCommandResultRef / lateSettlementCommandResultSchemaVersion;
    lateSettlementCommandResultDigestAlgorithmVersion / lateSettlementCommandResultDigest }
ledgerEffect =
  { state=none }
  | { state=applied; ledgerEffectRef / ledgerEffectSchemaVersion;
      ledgerEffectDigestAlgorithmVersion / ledgerEffectDigest;
      settlementEconomicIdentity=SettlementEconomicIdentity@1; amountDecimal }
resultingSettlementState = {
  toStateRevision;
  settledInput = BillingSettlementInputPointer@1
}
recordedAt

Fact 的严格 Candidate 必须逐项绑定预分配 Case Ref/Generation/Reservation、Current Resolved Exposure、Transition或 Command Result、可选 Ledger Effect与结算后 State;Fact顶层、Resolved Exposure、Resulting Pointer,以及 Applied Ledger Effect的 SettlementEconomicIdentity必须完全相等。两个 Source分支和两个 Ledger分支严格互斥。resolutionSource=late_settlement_command 时,Resolved Exposure必须以该命令授权的 Current Open Exposure为直接前驱、resolutionKind=settledresolutionOperationId=lateSettlementOperationIdresolutionSource=settlement_transition 时,首次解决以同代 Current Open Exposure为直接前驱,若 Case Link已经为 resolving且后续仍是零/负 Delta,则允许以同代 Current Resolved Exposure为直接前驱追加下一 Revision并固定 resolutionKind=no_longer_positive。该分支的 resolutionOperationId必须由 Contracts固定 Namespace按 (billingTransitionOperationId, caseGeneration, predecessor Exposure完整四元组/Revision)确定性派生,Worker不能随机生成。任何分支都不能跳过直接前驱、回指更早 Open造成分叉、跨 Generation/Reservation或把已解决 Exposure交给 Late Command。Fact Digest覆盖除 Repository-owned Ref/Time、Digest自身和 Idempotency Key外的完整 Candidate;Idempotency Key仅由 Schema Version + Digest Algorithm Version + Digest的 Contracts规范摘要生成。Billing Repository在同一 Ledger/State事务重算 Digest/Key、insert-or-compare,并拒绝 Case Ref/Generation/Reservation不等、跨 Case/Exposure/Dimension、跨 Credits/Entitlement Bucket/Unit/Currency、未解决 Exposure、Transition/Command Result与 Ledger/State不一致、同 Key异 Candidate或调用者提交 ID/Time/Key。Contracts发布完整 DTO、Canonical Bytes/Digest Golden与两类 Source、三类经济身份、两类 Ledger Effect,以及 Open→Resolved、Resolved→Resolved合法直接后继和回指旧Open分叉的正负向测试。

Billing 提供定向 readBillingCaseResolutionApplied:请求必须携带 Fact完整四元组、期望 Account/Reservation/Run/Step/Attempt/Dimension、Case Ref/Generation/Identity Reservation完整四元组与完整 Resolved Exposure四元组/Revision;只有 Operations Case Consumer的精确 Workload Audience/Scope可调用。响应是 {result=found; fact=BillingCaseResolutionApplied@1} | {result=not_found} | {result=conflicting; conflictRef};服务端和读方都要严格校验 Schema、算法、摘要、Repository绑定、Source专属前驱规则与全部期望身份,不能只信 Outbox Payload或裸 Ref。

Operations 消费 Outbox 后按预分配 Case Ref/Generation与 Case已冻结的 Current Exposure Source Revision 单调应用,而不是拿创建回执时的 Case Revision做异步硬 CAS。若一个或多个 Resolution Applied比 Case Create先到,Operations必须先验证 Case Identity Reservation、Applied Fact及 Exposure直接前驱链;只有全部可读且验证通过后,才能把每个结果按 (Case Generation, Case Ref, Exposure Revision, Applied Fact完整四元组)耐久保存为 pending_resolution,并在同一事务写该 Event 的 Applied Receipt。Owner Fact暂不可读或中间Exposure链暂不完整时不得写 pending_resolution、Outcome或Receipt,Consumer必须返回 retryable,使原 Event Delivery保持非终态并沿既有Lease/Attempt机制重试。后到 Create在同一事务选择最高连续、已验证 Revision,直接创建为绑定该 Fact的 Resolved Case,禁止提交中间 Open。Case已存在且 Source与 Fact Exposure Revision相等时,任何非终态 open | investigating | awaiting_external_evidence | resolution_proposed | approval_required | resolving 都必须在 Operations本地事务按当时 Case Revision重试 CAS至 resolving→resolved;Case已绑定更高 Exposure Revision时把旧 Fact耐久记录为 superseded_noop。Case仍低于该 Revision时,只有 Owner Read证明从 Case Source到 Fact Exposure是同代、同Reservation、无缺口的直接后继链,才可在同一事务推进 Source Revision、提交Outcome与Receipt并保持/进入 resolved。已经 Resolved的 Case收到更高合法 Resolved Revision时追加Timeline并原子更新 Source Binding,状态保持 Resolved,不把“同状态的新权威Revision”误判为冲突。同 Revision已由同 Fact解决时返回 already_applied,同 Revision绑定其他 Fact或 rejected则作为权威经济事实冲突隔离,不能静默覆盖。由于暂态缺证不会产生Receipt,Mandatory Delivery Non-terminal Count会自然阻断Completeness和Cutover,不再需要另建缺乏唤醒源的 deferred 终态。

sourceOwner=billing + sourceKind=late_settlement 的 Case 采用更严格的终态Refinement:Operations可以调查、补证、提出方案、审批和进入resolving,但不能凭本地“无动作关闭”、普通Case命令或评论把它推进为resolved | rejected。首期唯一合法终态来源是Billing Owner提交的BillingCaseResolutionApplied@1:要么新的累计输入已使正差额不再存在,要么获批Late Settlement Command已真实写入账本。平台承担、供应损失或核销若未来需要关闭该类Case,必须先另行定义Billing-owned Disposition Command、Ledger Effect与Applied Fact;本阶段没有这条命令,Admin不得用自由理由替代。这样Billing的opening | open | resolving Link与Operations终态始终由同一经济事实收敛,不会出现Operations已本地拒绝、Billing仍沿用同代Case的双Owner分叉。

每种 Outcome都以 (Case Generation, Case Ref, Resolved Exposure Revision, Applied Fact完整四元组)幂等保存,不能因评论、审批、调查、等待外部证据或指派增加了 Case Revision/改变了非终态而永远失败。投影或本地 Case CAS失败只重试消费,不能回滚已提交账本,也不会丢失待处理状态。若降额在 Case Create/Ack前到达,Billing追加更高 Exposure Revision的取消或解决事实;Operations由 Reservation预先认识稳定 Case Ref,并按 Source Revision收敛为 Resolved,晚到旧 Create/Ack只能返回 already_resolved | superseded_noop,绝不能恢复为 Open。若 Receipt后又到达新 Snapshot,它属于下一次 post-boundary correction;待批期间继续相对未变化的 settledInput重算暴露,连续降至零或负值时通过同一 Applied Fact链追加同代 Resolved Revision。获批的 LateSettlementCommand@1还必须在 Core签发 Grant时绑定 Case当时的 Revision和已冻结的同一 Current Open Exposure,并重新取得 purpose=late_settlement_command的 Current Receipt,才可把 settledInput推进到已经记录的 lastEvaluatedInput;该 Revision是签发授权边界,不是 Operations日后消费 Applied Fact的 CAS前提。契约测试必须覆盖本地终态命令与新Open Exposure、Billing Applied Fact并发时本地命令恒定拒绝,以及Applied先于Create、Create先于Applied两种顺序都只得到一个Owner驱动终态。

LateSettlementExposure@1 是 Billing-owned 的严格 Current Chain,不是 Operations Case 的别名:

lateSettlementExposureRef / lateSettlementExposureSchemaVersion
lateSettlementExposureDigestAlgorithmVersion = jcs-sha256-v1
lateSettlementExposureDigest / exposureRevision
billingReservationId / billingAccountId
runId / runStepId / executionAttemptId / chargeDimensionKey
pricingSnapshotId / billingPolicyRevisionId
settledSettlementInputSnapshotRef / settledSettlementInputSnapshotSchemaVersion
settledSettlementInputSnapshotDigestAlgorithmVersion / settledSettlementInputSnapshotDigest
currentSettlementInputSnapshotRef / currentSettlementInputSnapshotSchemaVersion
currentSettlementInputSnapshotDigestAlgorithmVersion / currentSettlementInputSnapshotDigest
currentEligibilityDecisionRef / currentEligibilityDecisionSchemaVersion
currentMeterEventHead = { state=none } | { state=available; meterEventRef; meterEventSchemaVersion }
previousSettledAmountDecimal / currentTargetAmountDecimal
settlementEconomicIdentity = SettlementEconomicIdentity@1
state =
  { kind=open; positiveDeltaDecimal }
  | { kind=resolved; resolutionKind=no_longer_positive|settled; resolutionOperationId }
supersedesExposure = { state=root } | { state=supersedes; ref; schemaVersion; digestAlgorithmVersion; digest; revision }
recordedAt

摘要覆盖除 Repository Ref/Time与摘要自身外的完整严格 Candidate。每个 Reservation/Attempt/Dimension只允许一个 Root和一个 Current Pointer;后继必须指向当前直接前驱并以 CAS推进,拒绝第二 Root、分叉、跳过 Revision、跨经济身份替代和同 Ref重绑。Open要求正差额精确等于 Target−Settled,且 Exposure、Settled/Current Pointer、Dimension State与 Reservation消费来源的 SettlementEconomicIdentity@1逐项相等;Resolved禁止正差额字段并固定 Resolution Operation。Settlement Transition可以按同一 Case Generation形成 Open→Resolved或在连续零/负更正中形成 Resolved→Resolved直接后继;每一代都必须引用前一 Current Exposure,不能反复回指原 Open。Late Command则只能绑定 Current state=open的完整 Exposure四元组/Revision,并且只创建该 Open的直接 Resolved后继。随后生成的 BillingCaseResolutionApplied@1单向引用每个 Resolved Exposure,Exposure不反向引用 Applied Fact,避免摘要环。Operations Case/Approval只能引用该代 Current Open Exposure;Case已 resolving/resolved后的新正 Exposure使用下一 Case Generation,不能从 Case文案或当前账本重新推算金额。

BillingSettlementTransition@1 是封闭 DTO:

billingSettlementTransitionRef / billingSettlementTransitionSchemaVersion
billingSettlementTransitionDigestAlgorithmVersion = jcs-sha256-v1
billingSettlementTransitionDigest
billingSettlementTransitionIdempotencyKey
billingTransitionOperationId
billingTransitionValidationOperationId
billingReservationId / billingAccountId / runId / runStepId / executionAttemptId / chargeDimensionKey
chargeType / chargeTypeRegistryVersion / unit
settlementEconomicIdentity = SettlementEconomicIdentity@1
fromStateRevision
fromLastEvaluatedInput = BillingSettlementInputPointer@1
fromSettledInput = BillingSettlementInputPointer@1
currentInput = BillingSettlementInputPointer@1
settlementInputLineage[] = ordered non-empty SettlementInputSnapshot full quartet / stateVersion
settlementInputConsumptionRef / settlementInputConsumptionSchemaVersion
settlementInputConsumptionDigestAlgorithmVersion / settlementInputConsumptionDigest
currentTargetAmountDecimal / deltaAmountDecimal
pricingSnapshotId / billingPolicyRevisionId
caseAction =
  { kind=none }
  | { kind=request_open; caseGeneration; caseRequestOperationId; reconciliationCaseRef;
      reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion;
      reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest;
      lateSettlementExposureRef / lateSettlementExposureSchemaVersion;
      lateSettlementExposureDigestAlgorithmVersion / lateSettlementExposureDigest / exposureRevision }
  | { kind=resolve; caseGeneration; caseRequestOperationId; reconciliationCaseRef;
      reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion;
      reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest;
      resolutionOperationId;
      resolvedExposureRef / resolvedExposureSchemaVersion;
      resolvedExposureDigestAlgorithmVersion / resolvedExposureDigest / resolvedExposureRevision }
basisDigestAlgorithmVersion = jcs-sha256-v1
basisDigest
meteringValidationReceiptRef / meteringValidationReceiptSchemaVersion
meteringValidationReceiptDigestAlgorithmVersion / meteringValidationReceiptDigest
ledgerTransitionIdempotencyKey
ledgerEffect =
  { state=none }
  | { state=applied; ledgerEffectRef / ledgerEffectSchemaVersion;
      ledgerEffectDigestAlgorithmVersion / ledgerEffectDigest;
      settlementEconomicIdentity=SettlementEconomicIdentity@1; amountDecimal }
toStateRevision
recordedAt

构造顺序固定为单向链:若本轮会首次打开 Case,先取得不创建业务 Case 的 Identity Reservation;若本轮解决 Case,则在读取 Current Exposure后先按 (billingTransitionOperationId, caseGeneration, predecessor Exposure完整四元组/Revision)确定性派生 resolutionOperationId。然后以严格 Payload去除两个 Idempotency Key、Basis Algorithm/Digest、最终 Transition Digest Algorithm/Digest、尚未产生的 billingTransitionValidationOperationId、Consumption四元组、Metering Receipt、Repository Ref/Time、ledgerEffecttoStateRevision得到 BillingSettlementTransitionBasisCandidate;已经确定的 Case/Resolution字段进入 Basis。计算 Basis Digest后,按逻辑 Operation、Dimension、From State Revision与Basis完整摘要确定性派生 billingTransitionValidationOperationId,并以该验证尝试 Operation和Basis摘要调用 Metering purpose=post_finalization_transition。锁定 State并通过唯一输入消费检查后,在同一 Billing事务先生成包含逻辑/验证 Operation与Basis摘要、但不反向引用 Transition的 BillingSettlementInputConsumption@1,再由 Transition Schema Version + billingTransitionValidationOperationId + Basis Digest Algorithm/Digest + Receipt四元组 + Consumption四元组生成 Transition Idempotency Key,并由固定 Namespace + Transition Key生成 Ledger Key;最后对包含 Basis、两类Operation、Receipt、Consumption、两个 Key、Ledger Effect与 To State的严格完整 Fact Candidate生成最终 Transition Digest。链路固定为 Basis → Validation Operation → Receipt → Consumption → Transition → State;Validation Operation、Receipt、Consumption、Ledger Key或结果不能反向进入 Basis,Consumption不引用最终 Transition,Applied Case Fact只单向引用已完成 Transition,因此没有摘要环。Repository对输入消费唯一键和 (billingReservationId, executionAttemptId, chargeDimensionKey, fromStateRevision)都设置唯一约束;同输入重放走 already_evaluated,同 Key/同严格 Candidate返回首次 Fact与结果,同 Key异 Candidate或同 From Revision第二条分支进入 Reconciliation。Metering Receipt提交后、Billing提交前崩溃时,新 Worker由同一逻辑 Transition Operation、From State、Case Generation与前驱 Exposure必然重建相同 Resolution Operation/Basis/Validation Operation并取得原 Receipt,不能因随机 ID形成永久冲突。若另一合法边界先推进 State,失败方必须丢弃旧 Receipt作为未采用验证证据,重读 State与Operation Result;输入仍未消费时按新 From State重建Basis和新的 Validation Operation后重试,绝不能把旧 Receipt应用到新Revision,也不能因同一逻辑Operation下出现合法的新Basis而让Metering返回永久冲突。

Repository 在同一 Billing事务中重验 Receipt/State,追加 Consumption、Transition、Ledger、Exposure或可选 BillingCaseResolutionApplied@1 与 Outbox,并 CAS更新 State;Consumption、Ledger、Applied Fact或 Outbox失败则全部回滚,外部 Case更新不属于该事务。Receipt必须同时回显当前Fact的逻辑 billingTransitionOperationId、按From Revision/Basis确定性派生的 billingTransitionValidationOperationId和Basis完整摘要;旧From Revision签发但未采用的Receipt不能提交到新State。settlementInputLineage[]必须非空、按State Version严格递增:第一项的前驱五元组精确等于fromLastEvaluatedInput,每个后项的前驱等于数组前项,最后一项完整身份等于currentInput;Billing在提交前通过带完整四元组/State Version的Owner Read逐条重算正文,Receipt只验证末项仍为Current。Transition顶层、Consumption、三个 Input Pointer、Lineage每项、Exposure/Resolved Exposure、Ledger Effect与 To State的 SettlementEconomicIdentity@1必须逐项相等;delta<0的 Ledger Effect Amount必须精确等于 Delta且推进 Settled;delta=0必须 ledgerEffect.state=none;两者在 Case Link=none时必须 caseAction=none且禁止 Applied Fact,在 Case Link=opening|open|resolving时必须 caseAction=resolve并产生匹配的 Resolved Exposure/Applied Fact,包括 resolve-before-create,且所有对象的 Generation/Reservation/Case Ref必须等于当前 Link。delta>0在 Link=none时创建第一代、在 opening|open时沿用当前代、在 resolving时创建严格下一代;只能 Request Open对应已预分配 Ref的 Exposure Case且不得写 Ledger。历史/篡改 Snapshot或 Receipt、Purpose/Basis/Validation Operation错配、Lineage缺项/乱序/分叉、Case Generation/Ref/Reservation错配、Credits/Entitlement/Money分支混合、错 Entitlement Key/Unit或 Currency全部拒绝;但已经提交、完整相同的旧逻辑 Operation或已处于无分叉祖先链的 Consumption必须走恢复分支,不能被“当前 Revision更高”错误拒绝。

Billing另提供仅供该 Handler恢复提交结果的 readBillingSettlementTransitionByOperation(billingTransitionOperationId, expected billingReservationId/billingAccountId/runId/runStepId/executionAttemptId/chargeDimensionKey/SettlementInputSnapshot完整四元组/stateVersion);它不要求调用方预先知道只存在于已提交 Fact内的旧 Basis Digest或Validation Operation,结果为 found{transition完整四元组; fact=BillingSettlementTransition@1} | not_found | conflicting{conflictRef}。入口只接受当前已认证的 Billing Transition Workload;服务端按逻辑 Operation唯一索引读取后严格校验确定性派生值与期望 Account/Run/Step/Input,并重算已提交Fact内的Basis、Validation Operation与最终摘要;调用方同样比较完整身份,不提供 Operation列表或按“最新”读取。处理结果是封闭联合:applied返回首次 Transition与 Consumption四元组、原 To State Revision及可选 Current State Revision;already_evaluated返回原 Consumption四元组、evaluationSource、可用的原 Transition四元组和 Current State Revision,并保证零 State/Ledger/Exposure/Case/Outbox写入;冲突返回 Reconciliation Ref。旧 Revision仅在作为新执行候选时拒绝,已提交同逻辑 Operation/Input的精确响应恢复不受后继 State影响。Contracts发布完整 DTO、Consumption、Basis Candidate、两类Operation、Operation Read/Result与 Ledger Key的 Golden JSON/Canonical Bytes/Digest。测试覆盖 available+Ineligible M1→Eligible M2、Terminal-none I1→E2(正差额待批)→I3(异步解决 Case)、无历史 Case的 delta=0与负差额、I1→E2→E3(沿用同一 Case)、I1→E2→I3→E4(新 Case Generation且旧审批不可复用)、Billing停在S1而Metering在首轮处理前推进S2→S3时以完整Lineage一次catch-up到S3、Lineage中间项不可读/错摘要/分叉/漏项拒绝、Open O1→Resolved R2→Resolved R3连续零/负 Head且每次只引用直接前驱、H1→E2(待批)→I3(退款/零额)、同 Snapshot重复触发、错误派生 Operation拒绝、跨 Account/Run/Step错绑、Metering Receipt提交→Billing预提交崩溃→新 Worker确定性重建相同 Resolution Operation/Basis并恢复、Receipt提交→并发前驱推进→CAS失败→新Worker按新From State重建不同Validation Operation并成功收敛且旧Receipt不可采用、T1提交响应丢失→Worker崩溃→T2推进→新 Worker仅凭 Operation/Input重放T1仍返回原结果、另一触发器重读已被后继超越的同一 Consumption返回 already_evaluateddelta=0响应丢失重放、delta≠0并发重放、Case identity预留响应丢失、resolve-before-create、多条Resolution先于Create、create ack丢失、晚到旧代 create/ack、Receipt后新 Snapshot、并发 B→C、连续两个降额 Head、跨经济身份重放与退款 Ledger失败;最终 Ledger净额必须精确等于 settledInput.targetAmountDecimal且经济身份不变。

经济边界 Outbox 使用 Contracts 已注册的封闭事件而不是自由通知:成功终局必须先在同一事务形成 BillingFinalizationCommitted@1 Owner Fact,再追加 billing.finalization.committed@1;每个已应用 Transition 必须原子追加 billing.settlement-transition.committed@1,绑定 Transition/Consumption四元组、Dimension的 From/To Revision和 Causation;caseAction=request_open 同事务还追加 billing.late-settlement-exposure.case-requested@1,绑定 Case Ref/Generation/Identity Reservation、Exposure四元组/Revision;产生 Applied Fact时追加 billing.case-resolution.applied@1,绑定 Applied Fact、Case Generation/Reservation与 Resolved Exposure。Late Settlement Command成功事务另追加 billing.late-settlement-command-result.committed@1,绑定 Command Result与 Applied Fact四元组。每个 Transition Event 的 Causation 必须指向该维度直接前一个会推进 State Revision 的边界:首条指向 Finalization Committed Event,之后可指向前一 Settlement Transition Event或前一 Late Settlement Command Result Event,且前驱 toStateRevision 必须精确等于当前 fromStateRevision;不得跳过 Late Result、回指更早 Transition或用到达顺序猜补链路。每个 Event都带 Owner Aggregate Revision、Operation/Correlation/Causation和冻结 Delivery Set Definition;事件、Audit、业务事实、Ledger/State与 Outbox Append任一失败必须一起回滚。Operations只消费这些低敏自包含 Event并通过 Owner Read Contract解引用,未知 Event/Schema保持隔离,不能把已提交经济事实永久留在不可投影状态。

EligibilityDecision 是 Metering 追加的不可变、内容可验证决策,不是运行时布尔值:

eligibilityDecisionRef / eligibilityDecisionSchemaVersion
eligibilityDecisionIdempotencyKey
runId / runStepId / executionAttemptId / chargeDimensionKey
chargeType / chargeTypeRegistryVersion / unit
billingPolicyRevisionId
evidenceFinalizationRuleVersion
executionEligibility = {
  executionEligibilityFactRef / executionEligibilityFactSchemaVersion
  executionEligibilityFactDigestAlgorithmVersion = jcs-sha256-v1
  executionEligibilityFactDigest
}
usageBasis =
  { kind=gateway_provider_available;
    gatewayDeploymentId; providerEvidenceKind=provider_usage; evidenceDimensionKey=attempt_aggregate@1;
    availabilitySnapshotRef / availabilitySnapshotSchemaVersion;
    availabilitySnapshotDigestAlgorithmVersion / availabilitySnapshotDigest / availabilityStateVersion;
    providerUsageEvidenceRef / providerUsageEvidenceSchemaVersion;
    providerUsageEvidenceDigestAlgorithmVersion / providerUsageEvidenceDigest;
    attemptRouteBindingRef / attemptRouteBindingSchemaVersion;
    attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest;
    gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion;
    gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest }
  | { kind=gateway_provider_terminal_none;
    gatewayDeploymentId; providerEvidenceKind=provider_usage; evidenceDimensionKey=attempt_aggregate@1;
    availabilitySnapshotRef / availabilitySnapshotSchemaVersion;
    availabilitySnapshotDigestAlgorithmVersion / availabilitySnapshotDigest / availabilityStateVersion;
    attemptRouteBindingRef / attemptRouteBindingSchemaVersion;
    attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest;
    gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion;
    gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest;
    terminalState = not_reported | unavailable }
  | { kind=execution_not_dispatched;
    attemptNotDispatchedFactRef / attemptNotDispatchedFactSchemaVersion;
    attemptNotDispatchedFactDigestAlgorithmVersion / attemptNotDispatchedFactDigest;
    gatewayAttemptDispatchSlotId / gatewayPreBindingRequestId / gatewayDeploymentId;
    terminalState = no_provider_side_effect }
eligibilityValidationOperationId
availabilityValidation =
  { state=available;
    eligibilityAvailabilityValidationReceiptRef / eligibilityAvailabilityValidationReceiptSchemaVersion;
    eligibilityAvailabilityValidationReceiptDigestAlgorithmVersion / eligibilityAvailabilityValidationReceiptDigest }
  | { state=not_required; reason=no_gateway_execution }
basisDigestAlgorithmVersion = jcs-sha256-v1
basisDigest
inputDigestAlgorithmVersion = jcs-sha256-v1
inputDigest
status = eligible | ineligible
reasonCode  # Contracts 注册枚举
supersedesEligibilityDecisionRef? / supersedesEligibilityDecisionSchemaVersion?
decidedAt

ExecutionEligibilityFact@1 是 Execution 在单个 Attempt 的执行结果与输出资格均不可逆确定后追加的封闭事实:

executionEligibilityFactRef / executionEligibilityFactSchemaVersion
executionEligibilityFactDigestAlgorithmVersion = jcs-sha256-v1
executionEligibilityFactDigest
runId / runStepId / executionAttemptId
attemptExecutionManifestRef / attemptExecutionManifestSchemaVersion
attemptExecutionManifestDigestAlgorithmVersion / attemptExecutionManifestDigest
terminalState = succeeded | failed | cancelled | timed_out
dispatchDisposition =
  { state=gateway_bound;
    attemptRouteBindingRef / attemptRouteBindingSchemaVersion;
    attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest;
    gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion;
    gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest }
  | { state=not_dispatched;
      attemptNotDispatchedFactRef / attemptNotDispatchedFactSchemaVersion;
      attemptNotDispatchedFactDigestAlgorithmVersion / attemptNotDispatchedFactDigest;
      gatewayAttemptDispatchSlotId / gatewayPreBindingRequestId / gatewayDeploymentId }
outputEligibility =
  { state=registered;
    outputFactRef / outputFactSchemaVersion;
    outputFactDigestAlgorithmVersion / outputFactDigest }
  | { state=not_required;
      outputContractRef / outputContractSchemaVersion;
      outputContractDigestAlgorithmVersion / outputContractDigest }
  | { state=unavailable; reasonCode }
  | { state=failed;
      executionFailureFactRef / executionFailureFactSchemaVersion;
      executionFailureFactDigestAlgorithmVersion / executionFailureFactDigest }
finalizedAt

Output联合只冻结对应Owner Fact完整四元组:registered的资产集合由不可变Output Fact正文承诺,failed的低敏失败语义由ExecutionFailureFact@1正文承诺。Eligibility Fact与Event都不复制assetVersionRefs[]或可选诊断引用,避免资产列表、Error投影和资格事实形成第二套可漂移摘要;需要展开时必须按各自授权读取Owner Fact或Operations诊断投影。

AttemptNotDispatchedFact@1 由 Execution 在验证 Gateway GatewayPreBindingRejectionFact@1 后追加;它是封闭 DTO,不是对 Gateway 错误文案的解释:

AttemptNotDispatchedFact@1 = {
  attemptNotDispatchedFactRef / attemptNotDispatchedFactSchemaVersion
  attemptNotDispatchedFactDigestAlgorithmVersion = jcs-sha256-v1
  attemptNotDispatchedFactDigest
  runId / runStepId / executionAttemptId
  attemptExecutionManifestRef / attemptExecutionManifestSchemaVersion
  attemptExecutionManifestDigestAlgorithmVersion / attemptExecutionManifestDigest
  gatewayDeploymentId / gatewayAttemptDispatchSlotId / gatewayPreBindingRequestId
  gatewayPreBindingRejectionFactRef / gatewayPreBindingRejectionFactSchemaVersion
  gatewayPreBindingRejectionFactDigestAlgorithmVersion / gatewayPreBindingRejectionFactDigest
  rejectionReasonCode
  providerSideEffect = none
  finalizedAt
}

Fact Digest 覆盖除 Repository-owned Ref/Time和摘要自身外的完整严格 Candidate;每个 Attempt 最多一条,且 (executionAttemptId, gatewayAttemptDispatchSlotId) 都有唯一约束。Execution 必须通过 Gateway 的版本化 Read Contract 确认 Rejection Fact、Slot、Request、Attempt Manifest 四元组和 Deployment 逐项相等,Slot Current 终态为 terminal_rejected 且永远不能创建 Binding;ExecutionEligibilityFact 的 Not-dispatched 联合必须回显同一 Slot/Request/Deployment 并纳入自身摘要。它不是 Operational Observation,也不复制可选诊断;Gateway Rejection 未找到、算法/摘要/身份错配、Side Effect 为未知、Slot 仍 reserved/已 bound 或已存在 Binding 时,Execution 不能创建该 Fact。同一 Attempt 的拒绝响应丢失或重调只能从 Gateway 取回同一终态拒绝;需要继续执行必须创建新 Attempt,不能让原 Attempt 同时拥有 Not-dispatched 与 Binding。

ExecutionFailureFact@1 同样由 Execution 追加,且是失败资格分支唯一可用于结算判断的低敏规范事实;它不是 Provider 错误正文或 Operations 错误记录的副本:

ExecutionFailureFact@1 = {
  executionFailureFactRef / executionFailureFactSchemaVersion
  executionFailureFactDigestAlgorithmVersion = jcs-sha256-v1
  executionFailureFactDigest
  runId / runStepId / executionAttemptId
  attemptExecutionManifestRef / attemptExecutionManifestSchemaVersion
  attemptExecutionManifestDigestAlgorithmVersion / attemptExecutionManifestDigest
  terminalState = failed | cancelled | timed_out
  terminalReasonCode  # Contracts 注册的封闭枚举,不接受上游自由文本
  failureClass = execution_platform | gateway_transport | provider_execution | output_pipeline
  failurePhase = before_provider_submission | provider_submission | provider_processing |
                 result_retrieval | output_validation | output_registration
  providerSideEffectState = none | possible | confirmed
  terminalDetermination = attempt_irreversibly_closed
  retryDisposition = new_attempt_required | not_retryable
  finalizedAt
}

Fact Digest 覆盖除 Repository-owned Ref/Time 与摘要自身外的完整严格 Candidate;terminalReasonCode 的合法值及其 failureClass/failurePhase/providerSideEffectState/retryDisposition 组合由版本化 Contracts Registry 冻结,未知值或非法组合一律拒绝。每个 Attempt 最多一条 Failure Fact,且只能在该 Attempt 已不可逆关闭后写入;其 Run/Step、Attempt Manifest 和 Terminal State 必须与引用它的唯一 ExecutionEligibilityFact@1 逐项相等,succeeded、暂态失败、可继续 Poll 的超时以及自由错误正文均禁止进入该事实。原始 Provider Code、错误消息、堆栈、Prompt、URL、Credential、路由私密正文与资产内容只可进入受限诊断存储;ErrorOccurrence@1只能在Operations诊断投影中关联Attempt/Failure Fact,不进入ExecutionEligibilityFact@1、Eligibility Event或Failure Fact摘要。它不存在、迟到或被归档都不能阻塞Attempt/Run关闭或影响Billing。

每个 executionAttemptId 终身只能有一条 ExecutionEligibilityFact,且 Fact 的 Run/Step、Attempt Manifest、Dispatch Disposition 与 Terminal/Output 联合必须同一事务校验;没有 Current Pointer、Supersession、第二 Root 或原地修订。gateway_bound 必须完整引用该 Attempt 唯一 Binding/Route;not_dispatched 必须完整引用同 Attempt 的 AttemptNotDispatchedFact,且严格禁止 Binding/Route。outputEligibility=not_required 的 Output Contract 四元组必须逐项等于 Attempt Manifest 与 Run Admission Manifest,其他三个分支禁止该四元组;outputEligibility=failed 必须引用完整 Failure Fact 四元组,其他分支严格禁止该四元组。Execution 在事实仍可能变化时不得提前追加;Provider Success、Poll/Callback、Operational Observation 或暂态 Run Row 都不能代替资格或失败事实。任何迟到且与已追加 Fact 冲突的执行/输出声明只能形成内容寻址ExecutionEligibilityConflictFact@1,不能创建第二 Fact、悄悄改变客户资格或直接创建Operations Case。Contracts 发布两类 Execution Fact 的严格 Schema、Reason/组合枚举和 Golden Canonical Bytes/Digest。

Execution还必须在追加唯一Eligibility Fact的同一事务内追加Contracts注册的execution.attempt-eligibility.finalized@1 Event、冻结Delivery Set并创建metering_input_processor Mandatory Delivery;Fact、嵌套Not-dispatched/Failure Fact、Event或Outbox任一失败全部回滚。Event Payload封闭携带Tenant/Account/Reservation、Run/Step/Attempt、Attempt Manifest完整四元组、Execution Eligibility Fact完整四元组、Terminal State和严格Dispatch/Output分支所需的低敏Fact四元组;aggregateId=executionAttemptIdaggregateRevision=1。同Attempt不得产生第二个Eligibility Event,Event不能复制原始错误、Prompt或Provider私密正文。Execution在Fact提交后、同步调用Metering或返回响应前崩溃,Mandatory Delivery仍必须最终到达。

Metering 只能通过以下版本化契约读取完整低敏 DTO;请求、响应与嵌套 Not-dispatched / Failure Evidence 都由 Contracts 生成严格 Schema:

ExecutionEligibilityConflictFact@1 = {
  executionEligibilityConflictFactRef / executionEligibilityConflictFactSchemaVersion
  executionEligibilityConflictFactDigestAlgorithmVersion = jcs-sha256-v1
  executionEligibilityConflictFactDigest
  tenantId / billingReservationId / runId / runStepId / executionAttemptId
  expectedExecutionEligibilityFactRef / expectedExecutionEligibilityFactSchemaVersion
  expectedExecutionEligibilityFactDigestAlgorithmVersion / expectedExecutionEligibilityFactDigest
  expectedAttemptExecutionManifestRef / expectedAttemptExecutionManifestSchemaVersion
  expectedAttemptExecutionManifestDigestAlgorithmVersion / expectedAttemptExecutionManifestDigest
  conflictKind = repository_binding | canonical_digest | identity_scope | embedded_evidence
  reasonCode
  detectedAt
}

readExecutionEligibilityFact(
  executionEligibilityFactRef, executionEligibilityFactSchemaVersion,
  executionEligibilityFactDigestAlgorithmVersion, executionEligibilityFactDigest,
  expectedTenantId, expectedBillingReservationId,
  expectedRunId, expectedRunStepId, expectedExecutionAttemptId,
  expectedAttemptExecutionManifestRef, expectedAttemptExecutionManifestSchemaVersion,
  expectedAttemptExecutionManifestDigestAlgorithmVersion,
  expectedAttemptExecutionManifestDigest
)
  -> { result=found;
       fact=ExecutionEligibilityFact@1;
       notDispatchedEvidence=
         { state=not_applicable }
         | { state=available; fact=AttemptNotDispatchedFact@1 };
       failureEvidence=
         { state=not_applicable }
         | { state=available; fact=ExecutionFailureFact@1 } }
   | { result=not_found }
   | { result=conflicting;
       executionEligibilityConflictFactRef / executionEligibilityConflictFactSchemaVersion;
       executionEligibilityConflictFactDigestAlgorithmVersion / executionEligibilityConflictFactDigest }

fact.dispatchDisposition=gateway_bound 强制 notDispatchedEvidence.state=not_applicablenot_dispatched 强制 available,且嵌套 Fact 自身四元组、Run/Step/Attempt、Attempt Manifest、Slot/Request/Deployment 必须与 Eligibility Fact 逐项相等。fact.outputEligibility=failed 强制 failureEvidence.state=available、其四元组等于 Output 联合且 Run/Step/Attempt、Attempt Manifest、Terminal State 与 Eligibility Fact 逐项相等;其他 Output 分支强制 failureEvidence.state=not_applicable。Owner Read 不返回原始错误正文或受限诊断载荷,也不允许按 Attempt、时间或“最新失败”列表读取。Execution 服务先按精确 Metering Workload Audience、Tenant/Reservation/Run/Step/Attempt Scope校验调用方,再验证请求四元组、Repository 不可变绑定并重算所有返回事实摘要;Metering 仍须严格解码响应。未知字段/Schema/算法、四元组缺项、Ref 重绑、错 Audience/身份、错误 Refinement 和半裁剪嵌套 Fact 均 fail-closed,不降格成 not_foundconflicting必须返回Execution Owner已持久化、内容寻址且绑定全部Expected身份的ExecutionEligibilityConflictFact@1,不得临时分配Operations Case Ref。由于一 Attempt 一条不可修订 Fact,读取后不存在旧 Head 与新 Head 竞态;Run 关闭事实还必须把每个 Attempt 对应的 Eligibility Fact 四元组纳入其集合。Contracts 发布两类 Dispatch、四类 Output、三个 Read Result、Conflict Fact、Canonical Bytes/Digest,以及跨 Tenant/Reservation/Run/Step/Attempt/Manifest/Slot/Request/Deployment、失败分支缺证据、Terminal/Reason 组合非法和嵌套 Fact 篡改的正负向测试。

Execution / Gateway → Metering 必达输入 Fence

Metering不依赖Execution直调、Gateway响应、Poll或Operational Observation碰巧到达。execution.attempt-eligibility.finalized@1gateway.evidence-availability.current-changed@1共享metering_input_processor Mandatory Destination。Consumer严格验证各自Envelope后,在单一Metering Inbox事务按Attempt锁定或创建MeteringAttemptInputFence@1、单调推进对应Watermark、按规范Trigger Set生成新Work Generation,并与Versioned Consumer Receipt一起提交后才Ack。Fence是本地协调状态,最低字段为:

MeteringAttemptInputFence@1 = {
  executionAttemptId
  executionContext =
    { state=unknown }
    | { state=known; runId; runStepId;
        attemptExecutionManifestRef / attemptExecutionManifestSchemaVersion;
        attemptExecutionManifestDigestAlgorithmVersion / attemptExecutionManifestDigest }
  eligibilityLane = {
    executionEligibilityWatermark =
      { state=none }
      | { state=finalized; eventId; eventEnvelopeDigestAlgorithmVersion; eventEnvelopeSha256;
          executionEligibilityFactRef / executionEligibilityFactSchemaVersion;
          executionEligibilityFactDigestAlgorithmVersion / executionEligibilityFactDigest };
    usageAvailabilityWatermark =
      { state=none }
      | { state=observed; gatewayDeploymentId; evidenceDimensionKey;
          eventId; eventEnvelopeDigestAlgorithmVersion; eventEnvelopeSha256;
          availabilityRef / availabilitySchemaVersion / availabilityDigestAlgorithmVersion / availabilityDigest / stateVersion };
    eligibilityTriggerDigestAlgorithmVersion=jcs-sha256-v1;
    eligibilityTriggerDigest / eligibilityFenceRevision / latestEligibilityWorkGeneration;
    coordinationState =
      { state=waiting_for_execution|waiting_for_gateway_usage; activeWork={ state=none };
        lastFinishedWork =
          { state=none }
          | { state=available;
              meteringInputWorkAttemptFinishedRef / meteringInputWorkAttemptFinishedSchemaVersion;
              meteringInputWorkAttemptFinishedDigestAlgorithmVersion / meteringInputWorkAttemptFinishedDigest } }
      | { state=pending;
          activeWork={ state=pending; workGeneration; meteringInputWorkOperationId;
            meteringInputWorkRef / meteringInputWorkSchemaVersion;
            meteringInputWorkDigestAlgorithmVersion / meteringInputWorkDigest } }
      | { state=applied; activeWork={ state=none };
          appliedWorkGeneration;
          meteringInputWorkAttemptFinishedRef / meteringInputWorkAttemptFinishedSchemaVersion;
          meteringInputWorkAttemptFinishedDigestAlgorithmVersion / meteringInputWorkAttemptFinishedDigest }
      | { state=blocked_on_owner_conflict;
          activeWork={ state=pending; workGeneration; meteringInputWorkOperationId;
            meteringInputWorkRef / meteringInputWorkSchemaVersion;
            meteringInputWorkDigestAlgorithmVersion / meteringInputWorkDigest };
          meteringInputWorkAttemptBlockedRef / meteringInputWorkAttemptBlockedSchemaVersion;
          meteringInputWorkAttemptBlockedDigestAlgorithmVersion / meteringInputWorkAttemptBlockedDigest }
  }
  providerCostLanes[] = sorted {
    gatewayDeploymentId / sourceEvidenceDimensionKey;
    costAvailabilityWatermark = { eventId; eventEnvelopeDigestAlgorithmVersion; eventEnvelopeSha256;
      availabilityRef / availabilitySchemaVersion / availabilityDigestAlgorithmVersion / availabilityDigest / stateVersion };
    providerCostTriggerDigestAlgorithmVersion=jcs-sha256-v1;
    providerCostTriggerDigest / providerCostFenceRevision / latestProviderCostWorkGeneration;
    coordinationState =
      { state=pending;
        activeWork={ state=pending; workGeneration; meteringInputWorkOperationId;
          meteringInputWorkRef / meteringInputWorkSchemaVersion;
          meteringInputWorkDigestAlgorithmVersion / meteringInputWorkDigest } }
      | { state=availability_processed; processingOutcome=available_applied;
          activeWork={ state=none };
          providerCostAvailabilityProcessingRef / providerCostAvailabilityProcessingSchemaVersion;
          providerCostAvailabilityProcessingDigestAlgorithmVersion / providerCostAvailabilityProcessingDigest;
          meteringInputWorkAttemptFinishedRef / meteringInputWorkAttemptFinishedSchemaVersion;
          meteringInputWorkAttemptFinishedDigestAlgorithmVersion / meteringInputWorkAttemptFinishedDigest }
      | { state=waiting_for_evidence; processingOutcome=waiting_for_evidence;
          activeWork={ state=none };
          providerCostAvailabilityProcessingRef / providerCostAvailabilityProcessingSchemaVersion;
          providerCostAvailabilityProcessingDigestAlgorithmVersion / providerCostAvailabilityProcessingDigest;
          meteringInputWorkAttemptFinishedRef / meteringInputWorkAttemptFinishedSchemaVersion;
          meteringInputWorkAttemptFinishedDigestAlgorithmVersion / meteringInputWorkAttemptFinishedDigest }
      | { state=no_cost_evidence; processingOutcome=no_cost_evidence;
          activeWork={ state=none };
          providerCostAvailabilityProcessingRef / providerCostAvailabilityProcessingSchemaVersion;
          providerCostAvailabilityProcessingDigestAlgorithmVersion / providerCostAvailabilityProcessingDigest;
          meteringInputWorkAttemptFinishedRef / meteringInputWorkAttemptFinishedSchemaVersion;
          meteringInputWorkAttemptFinishedDigestAlgorithmVersion / meteringInputWorkAttemptFinishedDigest }
      | { state=reconciliation_required; processingOutcome=reconciliation_required;
          activeWork={ state=none };
          providerCostAvailabilityProcessingRef / providerCostAvailabilityProcessingSchemaVersion;
          providerCostAvailabilityProcessingDigestAlgorithmVersion / providerCostAvailabilityProcessingDigest;
          meteringInputWorkAttemptFinishedRef / meteringInputWorkAttemptFinishedSchemaVersion;
          meteringInputWorkAttemptFinishedDigestAlgorithmVersion / meteringInputWorkAttemptFinishedDigest }
      | { state=blocked_on_processing_conflict;
          activeWork={ state=pending; workGeneration; meteringInputWorkOperationId;
            meteringInputWorkRef / meteringInputWorkSchemaVersion;
            meteringInputWorkDigestAlgorithmVersion / meteringInputWorkDigest };
          meteringInputWorkAttemptBlockedRef / meteringInputWorkAttemptBlockedSchemaVersion;
          meteringInputWorkAttemptBlockedDigestAlgorithmVersion / meteringInputWorkAttemptBlockedDigest }
  }
}

MeteringInputWork@1 = {
  meteringInputWorkRef / meteringInputWorkSchemaVersion
  meteringInputWorkDigestAlgorithmVersion = jcs-sha256-v1
  meteringInputWorkDigest / workGeneration
  executionAttemptId
  lane =
    { kind=eligibility_and_settlement_input }
    | { kind=provider_cost; gatewayDeploymentId; sourceEvidenceDimensionKey }
  laneTriggerDigestAlgorithmVersion / laneTriggerDigest
  meteringInputWorkOperationId
  sourceEventCoordinates[] = sorted { eventId; eventType; eventSchemaVersion;
    eventEnvelopeDigestAlgorithmVersion; eventEnvelopeSha256 }
  createdAt
}

MeteringInputWorkAttemptStarted@1 = {
  meteringInputWorkAttemptStartedRef / meteringInputWorkAttemptStartedSchemaVersion
  meteringInputWorkAttemptStartedDigestAlgorithmVersion = jcs-sha256-v1
  meteringInputWorkAttemptStartedDigest
  meteringInputWorkRef / meteringInputWorkSchemaVersion
  meteringInputWorkDigestAlgorithmVersion / meteringInputWorkDigest
  meteringInputWorkOperationId
  executionAttemptId / workGeneration
  lane = MeteringInputWork@1.lane
  laneTriggerDigestAlgorithmVersion / laneTriggerDigest
  attemptNumber / fencingToken / leaseExpiresAt / startedAt
}

EligibilityProcessingConflictEvidence@1 = {
  eligibilityProcessingConflictEvidenceRef / eligibilityProcessingConflictEvidenceSchemaVersion
  eligibilityProcessingConflictEvidenceDigestAlgorithmVersion = jcs-sha256-v1
  eligibilityProcessingConflictEvidenceDigest
  runId / runStepId / executionAttemptId
  attemptExecutionManifestRef / attemptExecutionManifestSchemaVersion
  attemptExecutionManifestDigestAlgorithmVersion / attemptExecutionManifestDigest
  executionEligibilityFactRef / executionEligibilityFactSchemaVersion
  executionEligibilityFactDigestAlgorithmVersion / executionEligibilityFactDigest
  meteringInputWorkRef / meteringInputWorkSchemaVersion
  meteringInputWorkDigestAlgorithmVersion / meteringInputWorkDigest
  meteringInputWorkOperationId / workGeneration
  laneTriggerDigestAlgorithmVersion / laneTriggerDigest
  validationStage = eligibility_basis | availability_validation_receipt |
    eligibility_decision | meter_event | settlement_input
  conflictingCandidateDigestAlgorithmVersion / conflictingCandidateDigest
  reasonCode
  observedAt
}

ProviderCostProcessingConflictEvidence@1 = {
  providerCostProcessingConflictEvidenceRef / providerCostProcessingConflictEvidenceSchemaVersion
  providerCostProcessingConflictEvidenceDigestAlgorithmVersion = jcs-sha256-v1
  providerCostProcessingConflictEvidenceDigest
  meteringInputWorkRef / meteringInputWorkSchemaVersion
  meteringInputWorkDigestAlgorithmVersion / meteringInputWorkDigest
  meteringInputWorkOperationId / workGeneration
  runId / runStepId / executionAttemptId / gatewayDeploymentId / sourceEvidenceDimensionKey
  attemptExecutionManifestRef / attemptExecutionManifestSchemaVersion
  attemptExecutionManifestDigestAlgorithmVersion / attemptExecutionManifestDigest
  attemptRouteBindingRef / attemptRouteBindingSchemaVersion
  attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest
  gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion
  gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest
  costAvailabilityRef / costAvailabilitySchemaVersion
  costAvailabilityDigestAlgorithmVersion / costAvailabilityDigest / costAvailabilityStateVersion
  cause =
    { kind=gateway_owner_conflict;
      gatewayEvidenceConflictFactRef / gatewayEvidenceConflictFactSchemaVersion;
      gatewayEvidenceConflictFactDigestAlgorithmVersion / gatewayEvidenceConflictFactDigest }
    | { kind=gateway_binding_owner_conflict;
        gatewayBindingConflictFactRef / gatewayBindingConflictFactSchemaVersion;
        gatewayBindingConflictFactDigestAlgorithmVersion / gatewayBindingConflictFactDigest }
    | { kind=metering_deterministic_validation;
        validationStage=source_schema|source_identity|provider_cost_mapping|
          provider_cost_normalization|availability_validation_receipt|
          route_validation_receipt|provider_cost_repository;
        conflictingCandidateDigestAlgorithmVersion / conflictingCandidateDigest;
        reasonCode }
  detectedAt
}

MeteringInputWorkAttemptBlocked@1 = {
  meteringInputWorkAttemptBlockedRef / meteringInputWorkAttemptBlockedSchemaVersion
  meteringInputWorkAttemptBlockedDigestAlgorithmVersion = jcs-sha256-v1
  meteringInputWorkAttemptBlockedDigest
  meteringInputWorkRef / meteringInputWorkSchemaVersion
  meteringInputWorkDigestAlgorithmVersion / meteringInputWorkDigest
  meteringInputWorkOperationId
  executionAttemptId / workGeneration
  lane = MeteringInputWork@1.lane
  laneTriggerDigestAlgorithmVersion / laneTriggerDigest
  attemptNumber / fencingToken
  blockingCause =
    { kind=execution_eligibility_owner_conflict;
      executionEligibilityConflictFactRef / executionEligibilityConflictFactSchemaVersion;
      executionEligibilityConflictFactDigestAlgorithmVersion / executionEligibilityConflictFactDigest }
    | { kind=gateway_usage_owner_conflict;
        gatewayEvidenceConflictFactRef / gatewayEvidenceConflictFactSchemaVersion;
        gatewayEvidenceConflictFactDigestAlgorithmVersion / gatewayEvidenceConflictFactDigest }
    | { kind=gateway_binding_owner_conflict;
        gatewayBindingConflictFactRef / gatewayBindingConflictFactSchemaVersion;
        gatewayBindingConflictFactDigestAlgorithmVersion / gatewayBindingConflictFactDigest }
    | { kind=eligibility_processing_conflict;
        eligibilityProcessingConflictEvidenceRef / eligibilityProcessingConflictEvidenceSchemaVersion;
        eligibilityProcessingConflictEvidenceDigestAlgorithmVersion / eligibilityProcessingConflictEvidenceDigest }
    | { kind=provider_cost_processing_conflict;
        providerCostProcessingConflictEvidenceRef / providerCostProcessingConflictEvidenceSchemaVersion;
        providerCostProcessingConflictEvidenceDigestAlgorithmVersion / providerCostProcessingConflictEvidenceDigest }
  blockedAt
}

ProviderCostAvailabilityProcessingFact@1 = {
  providerCostAvailabilityProcessingRef / providerCostAvailabilityProcessingSchemaVersion
  providerCostAvailabilityProcessingDigestAlgorithmVersion = jcs-sha256-v1
  providerCostAvailabilityProcessingDigest
  runId / runStepId / executionAttemptId / gatewayDeploymentId / sourceEvidenceDimensionKey
  attemptExecutionManifestRef / attemptExecutionManifestSchemaVersion
  attemptExecutionManifestDigestAlgorithmVersion / attemptExecutionManifestDigest
  attemptRouteBindingRef / attemptRouteBindingSchemaVersion
  attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest
  gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion
  gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest
  costAvailabilityRef / costAvailabilitySchemaVersion
  costAvailabilityDigestAlgorithmVersion / costAvailabilityDigest / costAvailabilityStateVersion
  predecessorProcessing =
    { state=root }
    | { state=supersedes;
        providerCostAvailabilityProcessingRef / providerCostAvailabilityProcessingSchemaVersion;
        providerCostAvailabilityProcessingDigestAlgorithmVersion / providerCostAvailabilityProcessingDigest }
  outcome =
    { state=available_applied;
      providerCostFactRef / providerCostFactSchemaVersion;
      providerCostFactDigestAlgorithmVersion / providerCostFactDigest;
      availabilityValidationReceiptRef / availabilityValidationReceiptSchemaVersion;
      availabilityValidationReceiptDigestAlgorithmVersion / availabilityValidationReceiptDigest;
      routeValidationReceiptRef / routeValidationReceiptSchemaVersion;
      routeValidationReceiptDigestAlgorithmVersion / routeValidationReceiptDigest }
    | { state=waiting_for_evidence; availabilityState=pending; stateReasonCode }
    | { state=no_cost_evidence;
        availabilityState=not_reported|unavailable; stateReasonCode }
    | { state=reconciliation_required;
        availabilityState=conflicting; stateReasonCode }
  processedAt
}

MeteringInputWorkAttemptFinished@1 = {
  meteringInputWorkAttemptFinishedRef / meteringInputWorkAttemptFinishedSchemaVersion
  meteringInputWorkAttemptFinishedDigestAlgorithmVersion = jcs-sha256-v1
  meteringInputWorkAttemptFinishedDigest
  meteringInputWorkRef / meteringInputWorkSchemaVersion
  meteringInputWorkDigestAlgorithmVersion / meteringInputWorkDigest
  meteringInputWorkOperationId
  executionAttemptId / workGeneration
  lane = MeteringInputWork@1.lane
  laneTriggerDigestAlgorithmVersion / laneTriggerDigest
  attemptNumber / fencingToken
  outcome =
    { kind=eligibility_and_settlement_input;
      state=waiting_for_counterpart; waitingFor=execution_eligibility|gateway_usage }
    | { kind=eligibility_and_settlement_input; state=applied;
        settlementInputSnapshotCount;
        settlementInputSnapshots[] = sorted {
          runStepId / executionAttemptId / chargeDimensionKey;
          ref / schemaVersion / digestAlgorithmVersion / digest / stateVersion } }
    | { kind=provider_cost; state=availability_processed;
        providerCostAvailabilityProcessingRef / providerCostAvailabilityProcessingSchemaVersion;
        providerCostAvailabilityProcessingDigestAlgorithmVersion / providerCostAvailabilityProcessingDigest }
    | { kind=eligibility_and_settlement_input|provider_cost; state=superseded;
        supersededByWorkGeneration }
  finishedAt
}

readMeteringInputWorkByOperation(
  meteringInputWorkOperationId,
  expected executionAttemptId, lane
)
  -> { result=found; work=MeteringInputWork@1 }
   | { result=not_found }
   | { result=conflicting; conflictManifestRef / conflictManifestSchemaVersion;
       conflictManifestDigestAlgorithmVersion / conflictManifestDigest }

两类Trigger摘要和CAS域严格分离:eligibilityTriggerDigest只覆盖Attempt Manifest、Execution Eligibility与Usage Watermark;每个providerCostTriggerDigest只覆盖Attempt Manifest和该Cost Lane的Watermark,providerCostLanes[](gatewayDeploymentId, sourceEvidenceDimensionKey)排序。Cost Head推进不得改变Eligibility Revision/Generation,Usage或Execution推进也不得使已Claim Cost Work失效;共享Manifest只能insert-or-compare为同一四元组,不能借此把两条经济链串成共同完成门槛。Source Event坐标按Event Type/ID排序并拒绝重复。相同Event同摘要不增加对应Lane Generation,只有该Lane Watermark真实推进才生成新Work或在未Claim的同Trigger Work上insert-or-compare;同Revision异摘要、Manifest重绑或跨Attempt进入Conflict。meteringInputWorkOperationId由Contracts固定Namespace按executionAttemptId + lane严格身份 + workGeneration + laneTriggerDigest完整带算法值确定性派生并进入Work摘要,时间、Worker与Attempt不参与;Repository对(executionAttemptId, lane严格身份, workGeneration)和Operation分别唯一且insert-or-compare。Work正文和Digest不含可变状态;Claim事务锁定对应Lane/Work、递增attemptNumber与Fencing Token并追加唯一Started Fact,Finish严格绑定Work四元组、Operation、Generation、Lane、Trigger、Attempt Number与Token。pending必须且只能绑定本Lane最新Generation的Active Work;Eligibility成功必须原子推进为applied并绑定含完整Snapshot集合的Finish Fact,Cost完成必须清空Active Work、绑定Processing/Finish两份Fact,且availability_processed|waiting_for_evidence|no_cost_evidence|reconciliation_required分别只允许Processing outcome=available_applied|waiting_for_evidence|no_cost_evidence|reconciliation_required。数据库约束拒绝成功后仍Pending、完成态缺Fact、Outcome错配或新Head已推进却提交旧Trigger。旧Lease Owner不能提交,进程崩溃后由新Token恢复;扫描未完成Started或按Operation精确读取必须恢复原Work,不能创建新Operation,禁止可变单行把claimed直接改成applied来吞掉未知结果。Read只接受精确Metering Input Worker Audience/Attempt/Lane Scope且重算Work摘要与Operation,不提供latest、列表或跨Lane查询。settlementInputSnapshots[](runStepId, executionAttemptId, chargeDimensionKey)排序并与Count全等,且每条Snapshot与同事务Event/Mandatory Delivery必须由Repository Deferred Constraint双向校验。Contracts/Repository测试必须覆盖领取前、Started后、Owner Read后与业务提交后崩溃、响应丢失、Lease过期、新旧Token竞争、成功完成后扫描不重领、同Operation异Work以及跨Attempt/Lane恢复。

eligibility_and_settlement_input Work在Not-dispatched分支只需Execution Event;Gateway-bound分支必须同时拥有Execution Eligibility Watermark与目标Usage Availability Watermark。Worker以已接受Event摘要签发的精确Metering Workload Token调用readExecutionEligibilityFact、Execution的readAttemptExecutionManifest(purpose=metering_execution_eligibility)以及Gateway Binding/Availability/Evidence Read,严格重算全部摘要和Run/Step/Attempt/Deployment/Binding/Route/Policy身份,再按下文产生Eligibility Decision、可选MeterEvent及每个Charge Dimension的SettlementInput。provider_cost Work仅依赖Cost Availability Event和同一Manifest/Binding/Route,不等待Execution Eligibility或客户Settlement链;它用readAttemptExecutionManifest(purpose=metering_gateway_availability)仅凭Event、Attempt和Manifest四元组bootstrap Run/Step/Deployment后再验证正文。某一Usage输入先到时Eligibility Lane耐久保持waiting_for_counterpart,另一输入到达会用新的Eligibility Generation重新置Pending;Cost Lane始终独立,任何一边都不得因另一Lane变化重启或等待。

一代Usage Work只有在它所承诺的全部Charge Dimension Snapshot、各自metering.settlement-input.current-changed@1 Event与Mandatory Billing Delivery均同Metering事务提交,且锁定Fence后Eligibility Lane Watermark仍等于该eligibilityTriggerDigest时才能以state=applied完成。Cost Work必须先按Event精确读取Current Cost Availability,再在同一Metering事务追加唯一ProviderCostAvailabilityProcessingFact@1、对应Finish Fact并以Trigger CAS推进Cost Lane:available必须原子保存ProviderCostFact及两份Receipt后写available_appliedpendingwaiting_for_evidence并结束本代Work,等待后继Head Event创建新Generation;not_reported|unavailableno_cost_evidence,它只证明该Availability版本没有可规范化成本证据,绝不表示成本为零、也不创建ProviderCostFact;conflictingreconciliation_required,并以该Processing Fact自身完整四元组作为Metering-owned耐久对账锚点。本阶段只保证Metering Owner可按Lane/Fact定向诊断,并允许发送明确标注为不完整的best-effort告警;当前13-Pair Delivery Set没有Provider Cost Processing Event,因此不得声称Operations拥有完整异常队列,也不创建第三种ReconciliationCase、Identity Reservation或本地终态。未来若要进入Admin可靠队列,必须先新增Canonical Processing Event、Mandatory core_operations Delivery、Owner Read和投影契约。通用Incident可以引用该四元组,但不能修改Lane。只有Gateway在Owner侧修复后追加的后继Availability Current Event才能创建新Generation并重新处理,旧Processing Fact永久保留。Processing Fact按(executionAttemptId, gatewayDeploymentId, sourceEvidenceDimensionKey)维持唯一Root与Current直接前驱链,摘要绑定Manifest/Binding/Route、Availability五元组和严格Outcome;同Availability只能有一条同正文Fact,同Ref/Revision异摘要、非法状态/Outcome或Fact/Receipt错配冲突。任一Outcome提交时对应Cost Lane仍必须等于providerCostTriggerDigest,另一Lane推进不影响本次CAS;若同Lane Current已推进,旧Receipt/Fact/Work只作未采用证据且不得完成Lane,后继Mandatory Event负责新Generation。这样永久not_reported|unavailable不会无限重跑,pending不会忙轮询,未来Available Head仍能自然追加后继处理事实。Contracts/Repository/跨进程测试必须覆盖全部五种Availability分支、崩溃窗、分Lane Fencing Token竞争、事件乱序/重复、A1 Work与A2 Event并发、仅Cost推进时Usage Work不失效、仅Usage推进时Cost Work不失效、跨Attempt/Kind/Dimension/Manifest重放和“所有本Lane下游写入成功前不得完成Work”。

两个 Gateway Eligibility 分支都必须完整复制 availabilityIdentity.scope=attempt_bound 的 Attempt、Gateway Deployment、Binding/Route、Provider Usage Kind 与固定 Evidence Dimension,并要求 availabilityValidation.state=available 引用 purpose=eligibility_consumption 的 Current Receipt;gateway_provider_available 另外要求 Evidence Head,gateway_provider_terminal_none 则严格禁止 Evidence Ref,但仍保留其已冻结 Binding/Route。Terminal-none 只有在 Billing Policy 内的 evidenceFinalizationRuleVersion 已判定 not_reported | unavailable 为该 Attempt/Dimension 的终态后才能创建,且 status 必须为 ineligibleexecution_not_dispatched 不读取 Pre-binding Availability 或 Gateway Evidence:它必须与同一 ExecutionEligibilityFact.dispatchDisposition=not_dispatched 和唯一 AttemptNotDispatchedFact 四元组相等,要求 availabilityValidation={state=not_required; reason=no_gateway_execution}status 固定为 ineligible、目标用量固定为 none;任何 Binding/Route/Evidence/Availability Receipt 字段都禁止。Decision、FinalizationInputManifest 与 BillingFinalizationDecision 的 Rule Version 必须逐项等于同一 billingPolicyRevisionId 中摘要固定的版本;禁止读取当前 Rule。pending | conflicting 不能生成 Decision。三个分支均与 Decision 的 Attempt、Dimension 与 Charge Type 语义一致,未知 Usage Basis Kind 一律拒绝。

为避免 Key、Operation、Receipt 与 Decision 互相摘要,Metering 先构造严格 EligibilityDecisionBasisCandidate:它覆盖 Decision 的全部确定性业务输入和 availabilityValidation.state/reason,但明确排除 eligibilityDecisionIdempotencyKeyeligibilityValidationOperationId、Basis Digest Algorithm/Digest 自身、Validation Receipt 四元组、最终 inputDigest、Repository-owned Ref/Time。Metering 对该 Candidate 计算 basisDigest。Gateway两分支再由 Contracts固定Namespace按 purpose=eligibility_consumption + Basis带算法摘要 + eligibilityValidationScope 确定性派生eligibilityValidationOperationId;该Scope只使用Gateway可与冻结记录共同验证的Attempt、Gateway Deployment、Binding/Route完整四元组、Usage Kind/Dimension和Availability完整五元组,Run/Step/Charge Dimension/Billing Policy等业务语义由Basis摘要承诺而不重复塞入Gateway Token。execution_not_dispatched则用独立固定Namespace按purpose=no_gateway_execution + Basis带算法摘要 + AttemptNotDispatchedFact完整四元组 + Slot/Request/Deployment派生,避免让不存在的Gateway目标参与验证。Gateway分支以该Operation、Basis摘要和完整Scope调用purpose=eligibility_consumption,Receipt必须回显并摘要绑定全部三者与Availability Snapshot;Not-dispatched分支只通过Execution Read Contract验证唯一Fact,严格没有Gateway Receipt。最终inputDigest覆盖完整Basis Candidate、Basis Digest、eligibilityValidationOperationId与完整availabilityValidation联合;最后由eligibilityDecisionSchemaVersion + inputDigestAlgorithmVersion + inputDigest的Contracts Canonical摘要生成Idempotency Key。各步都固定为UTF-8 NFC、严格Schema、RFC 8785 Canonical JSON与SHA-256;链路是Basis Candidate → Basis Digest → Validation Operation → Receipt/no-receipt marker → Input Digest → Key/Decision,后继对象不反向进入前驱,因此没有摘要环。相同Basis/Scope在Receipt后崩溃或响应丢失时必须派生同一Operation并幂等取回首次Receipt;Basis或Availability Head变化必须派生新Operation。算法或字段集变化必须升级eligibilityDecisionSchemaVersion,MeterEvent引用端必须携带并精确匹配该版本;未知、错版本、错误派生Operation、Receipt Purpose/Operation/Basis/Scope摘要错配或分支/Validation联合错配均拒绝。

Decision 使用上述 Key 作为唯一 insert-or-compare 身份;eligibilityDecisionRef/decidedAt 只由 Repository 首次 insert 生成。Repository 重算 Basis/Input Digest 与 Key,并比较严格 Decision Candidate 的全部确定性字段;并发同 Candidate 返回首次 Ref/Time,不同 Payload/摘要或同 Key 异 Candidate写EligibilityProcessingConflictEvidence@1 + MeteringInputWorkAttemptBlocked@1并保留同一Active Work,eligibilityDecisionRef 不可被重新绑定,也不创建Case。billingPolicyRevisionId 来自准入时冻结的 RunAdmissionManifest;MeterEvent 中的 settlementEligibility.status + eligibilityDecisionRef + eligibilityDecisionSchemaVersion 必须与该 Decision 相等。

supersedesEligibilityDecisionRef + supersedesEligibilityDecisionSchemaVersion 必须同时存在或同时缺失。Repository 对 (runId, runStepId, executionAttemptId, chargeDimensionKey) 只允许一个 Chain Root;首个 Decision 必须两者皆无并冻结 Charge Type/Registry/Unit 与 Billing Policy。更正必须成对指向同一身份的当前直接前驱,且声明版本必须等于前驱不可变记录的 Schema Version。唯一约束、强外键和事务比较拒绝第二 Root、分叉、循环、跳过前驱、跨维度/Registry/策略替代、相同新 Input Digest 竞争绑定不同前驱以及旧 Head 乱序晚到;确定性失败进入上述Owner-local Conflict/Blocked状态,任何 Consumer 都不得自行挑选分支或创建Case。

迟到或更正的权威输入只能追加同一完整经济/计量身份的 Decision Supersession;有可计量用量时再由新的 MeterEvent Head 引用它,终态无 Evidence 的 Ineligible Decision 不伪造零用量 MeterEvent。Billing 把 Current Eligibility/MeterEvent 组合解释为该计量维度的“目标应结净额”,而不是收费命令。Reservation 终局前,eligible→ineligibleeligible→eligibleineligible→ineligibleineligible→eligible 与多次翻转都只改变 Finalization 将读取的 Current Head,客户账本保持无 Base/Adjustment/Refund;Finalization 锁定的最终 Eligible Head 才创建唯一 Base,最终 Ineligible 则该维度不写账。终局后的差额矩阵固定为:delta < 0 可按冻结 Policy 自动追加 Refund/负 Adjustment,delta = 0 幂等且不写账,delta > 0 一律创建 Billing-owned Late Settlement Exposure,并异步关联 Operations Reconciliation Case,不能自动扣款;因此新出现 Eligible、ineligible→eligibleeligible→eligible 增额都必须重新校验余额/授信与审批,只有降额、转 Ineligible 或合法退款可自动减少客户净额。策略更新后对同一历史 Attempt 重放必须得到相同 Policy Revision、Input Digest 与结果;缺失必需 Eligibility/Usage 输入时保持等待,任意 Ref/Version/Digest/Policy/Attempt/Dimension 错配、未知分支、非法 Supersession 或当前状态回查的确定性冲突写Owner-local Conflict/Blocked事实并阻止可结算输出。只有合法终局后正差额才按Late Settlement打开维度级正式Case。Contracts 必须分别覆盖终局前四类单步转换和 ineligible→eligible→ineligible→eligible 多次翻转、终局锁定时竞争更新,以及终局后每类资格转换的金额增加/相等/减少,证明每个维度至多一笔 Base、终局前无客户 Ledger、正差额从不绕过 Late Settlement,负差额后账本净额等于获准目标。

Repository 必须从持久 Candidate 重算 Basis、eligibilityValidationOperationId、Input Digest 与 Key,并验证 Receipt 中的 Operation 完全相等。Receipt 已提交但 Decision 未提交时,重启 Worker 用相同 Basis/Target取回原 Receipt;Decision 已提交但响应丢失时,相同 Key/Candidate返回首次 Ref/Time,不能创建第二 Root或伪造 Supersession。若 Current Availability/Eligibility 已合法推进,则新 Candidate使用新 Operation并只按 Current Head直接前驱规则追加更正。

blocked_on_owner_conflict|blocked_on_processing_conflict不是完成态:它们必须保留本Lane最新Generation的Active Work完整四元组与Operation,并绑定同Work/Trigger/Attempt/Fencing Token的MeteringInputWorkAttemptBlocked@1。Lane与Cause严格细化:eligibility_and_settlement_input只允许execution_eligibility_owner_conflict | gateway_usage_owner_conflict | gateway_binding_owner_conflict | eligibility_processing_conflictprovider_cost只允许外层provider_cost_processing_conflict,再由ProviderCostProcessingConflictEvidence@1.cause区分Gateway Evidence、Gateway Binding或Metering本地确定性校验。本地校验分支必须冻结冲突Candidate带算法摘要,不能只写reasonCode。当前Lease被释放但Work不写Finished、不清空、不推进成terminal CAS;Owner/本地配置修复后可按Operations Policy重领同Work,本Lane新Current Event则用新Generation原子supersede。暂态Owner/网络/锁/容量失败只记非领域Attempt Telemetry,同样不写Blocked/Finished/Processing Fact或CAS。这些状态均不分配Operations Case;Billing Finalization如果仍无法封闭本Run,只能由它自身已定义的run级Case Identity协议创建Case。

新Current Event在旧Work已经Started时,创建新Generation的Inbox事务必须以旧Work完整四元组、Started Attempt与旧Fencing Token做insert-or-compare,追加唯一outcome.state=superseded Finish;若该事务未代写,旧Worker晚到时只能在确认Lane Current已指向supersededByWorkGeneration后追加同一正文Finish。两条路径竞争返回同一内容寻址Fact,且都严禁旧Token写Eligibility、MeterEvent、SettlementInput、ProviderCostFact、Processing Fact或推进新Lane。未完成Started扫描遇到已有Superseded Finish立即停止,避免旧Attempt永久悬空。

Metering → Billing 的一致结算输入

Metering 与 Billing 保持独立逻辑Owner、Repository接口和写入权限;首期即使同处oceanway-core与一个PostgreSQL,Billing也不能跨模块Join Metering私表或靠“提交前再查一次 Current Row”偷换版本化契约,未来物理拆分时也无需改写经济语义。Metering 为每个 (billingReservationId, executionAttemptId, chargeDimensionKey) 维护唯一 Current SettlementInputSnapshot@1

SettlementInputConflictFact@1 = {
  settlementInputConflictFactRef / settlementInputConflictFactSchemaVersion
  settlementInputConflictFactDigestAlgorithmVersion = jcs-sha256-v1
  settlementInputConflictFactDigest
  tenantKind / tenantId / workspaceId / projectId?
  billingReservationId / billingAccountId / runId / runStepId
  executionAttemptId / chargeDimensionKey
  settlementEconomicIdentity = SettlementEconomicIdentity@1
  conflict =
    { kind=eligibility_meter_pairing;
      eligibilityHead=
        { state=none }
        | { state=available; ref / schemaVersion / digestAlgorithmVersion / digest };
      meterEventHead=
        { state=none }
        | { state=available; ref / schemaVersion / digestAlgorithmVersion / digest };
      reasonCode }
    | { kind=settlement_input_repository;
        conflictManifestRef / conflictManifestSchemaVersion;
        conflictManifestDigestAlgorithmVersion / conflictManifestDigest;
        reasonCode }
  detectedAt
}

SettlementInputSnapshot@1 = {
settlementInputSnapshotRef / settlementInputSnapshotSchemaVersion
settlementInputSnapshotDigestAlgorithmVersion = jcs-sha256-v1
settlementInputSnapshotDigest / stateVersion
billingReservationId / billingAccountId
tenantKind / tenantId / workspaceId / projectId?
actorPrincipalId / executionPrincipalId / serviceAccountId?
product / agentRevisionId?
runId / runStepId / executionAttemptId / chargeDimensionKey
modelOfferingRevisionId / modelDeploymentId / pricingSnapshotId
chargeType / chargeTypeRegistryVersion / unit
settlementEconomicIdentity = SettlementEconomicIdentity@1
billingPolicyRevisionId / evidenceFinalizationRuleVersion
resolution =
  { state=metered;
    eligibilityDecisionRef / eligibilityDecisionSchemaVersion;
    eligibilityDecisionInputDigestAlgorithmVersion / eligibilityDecisionInputDigest;
    eligibilityStatus=eligible|ineligible;
    executionEligibilityFactRef / executionEligibilityFactSchemaVersion;
    executionEligibilityFactDigestAlgorithmVersion / executionEligibilityFactDigest;
    meterEventRef / meterEventSchemaVersion;
    meterEventCandidateDigestAlgorithmVersion / meterEventCandidateDigest;
    quantityDecimal / unit;
    usageBasis =
      { kind=gateway_provider_available;
        gatewayDeploymentId; providerEvidenceKind=provider_usage; evidenceDimensionKey=attempt_aggregate@1;
        availabilitySnapshotRef / availabilitySnapshotSchemaVersion;
        availabilitySnapshotDigestAlgorithmVersion / availabilitySnapshotDigest / availabilityStateVersion;
        providerUsageEvidenceRef / providerUsageEvidenceSchemaVersion;
        providerUsageEvidenceDigestAlgorithmVersion / providerUsageEvidenceDigest;
        attemptRouteBindingRef / attemptRouteBindingSchemaVersion;
        attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest;
        gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion;
        gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest };
    availabilityValidation =
      { state=available;
        eligibilityAvailabilityValidationReceiptRef / eligibilityAvailabilityValidationReceiptSchemaVersion;
        eligibilityAvailabilityValidationReceiptDigestAlgorithmVersion / eligibilityAvailabilityValidationReceiptDigest }
      }
  | { state=terminal_none;
      eligibilityDecisionRef / eligibilityDecisionSchemaVersion;
      eligibilityDecisionInputDigestAlgorithmVersion / eligibilityDecisionInputDigest;
      eligibilityStatus=ineligible;
      executionEligibilityFactRef / executionEligibilityFactSchemaVersion;
      executionEligibilityFactDigestAlgorithmVersion / executionEligibilityFactDigest;
      meterEventHead={ state=none };
      usageBasis=
        { kind=gateway_provider_terminal_none;
          gatewayDeploymentId; providerEvidenceKind=provider_usage; evidenceDimensionKey=attempt_aggregate@1;
          availabilitySnapshotRef / availabilitySnapshotSchemaVersion;
          availabilitySnapshotDigestAlgorithmVersion / availabilitySnapshotDigest / availabilityStateVersion;
          attemptRouteBindingRef / attemptRouteBindingSchemaVersion;
          attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest;
          gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion;
          gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest;
          terminalState=not_reported|unavailable }
        | { kind=execution_not_dispatched;
            attemptNotDispatchedFactRef / attemptNotDispatchedFactSchemaVersion;
            attemptNotDispatchedFactDigestAlgorithmVersion / attemptNotDispatchedFactDigest;
            gatewayAttemptDispatchSlotId / gatewayPreBindingRequestId / gatewayDeploymentId;
            terminalState=no_provider_side_effect };
      availabilityValidation =
        { state=available;
          eligibilityAvailabilityValidationReceiptRef / eligibilityAvailabilityValidationReceiptSchemaVersion;
          eligibilityAvailabilityValidationReceiptDigestAlgorithmVersion / eligibilityAvailabilityValidationReceiptDigest }
        | { state=not_required; reason=no_gateway_execution } }
  | { state=unresolved; reasonCode;
      observedInput =
        { state=none }
        | { state=availability_only;
            availabilitySnapshotRef / availabilitySnapshotSchemaVersion;
            availabilitySnapshotDigestAlgorithmVersion / availabilitySnapshotDigest;
            availabilityStateVersion; providerEvidenceKind; evidenceDimensionKey }
        | { state=eligibility_without_matching_meter;
            eligibilityDecisionRef / eligibilityDecisionSchemaVersion;
            eligibilityDecisionInputDigestAlgorithmVersion / eligibilityDecisionInputDigest;
            eligibilityStatus=eligible|ineligible;
            executionEligibilityFactRef / executionEligibilityFactSchemaVersion;
            executionEligibilityFactDigestAlgorithmVersion / executionEligibilityFactDigest }
        | { state=conflicting;
            settlementInputConflictFactRef / settlementInputConflictFactSchemaVersion;
            settlementInputConflictFactDigestAlgorithmVersion / settlementInputConflictFactDigest } }
supersedesSettlementInputSnapshot =
  { state=root }
  | { state=supersedes; ref; schemaVersion; digestAlgorithmVersion; digest; stateVersion }
}

metered 必须把 MeterEvent 的 settlementEligibility 与 Eligibility Head 的 Ref/Version/Status 逐项相等,并把 Eligibility 所用 ExecutionEligibilityFact 四元组、MeterEvent Candidate Digest、规范 Quantity/Unit、Pricing/Policy、全部经济身份与原 Fact 精确复制到受摘要保护的低敏 DTO;其 Gateway Basis 与 availabilityValidation.state=available Receipt、Evidence/Binding/Route 必须和 Decision 逐项相等。Snapshot 的 SettlementEconomicIdentity@1 由冻结 Pricing/Billing Policy 对该 Reservation 消费来源解析,必须与 Admission BillingValue、后续 Billing Pointer/State/Ledger逐项相等;Metering 不能从 Usage unit 猜测它,也不能丢失 Entitlement Key 或 Money Currency。Billing 只通过该版本化 DTO 和自身不可变 Pricing Snapshot 计算 Target,不直连 Metering 数据库。Usage available 但 Ineligible 的 M1 仍是合法 metered,其 Billing 目标为零。terminal_none 必须引用 Current Ineligible Decision,且 Decision 的 ExecutionEligibilityFact、Usage Basis 与 Availability Validation 联合均与 Snapshot 完全相等:Gateway Terminal-none 要求 Receipt Available,Not-dispatched 要求 no-receipt marker;两者都不得携带 MeterEvent/Quantity。Availability 已变但新 Eligibility 尚未形成、Eligibility 已变但对应 MeterEvent 尚未形成、任一 Current Pointer 不一致或链路冲突都只能发布严格 unresolved,成功 Finalization 不得消费。Snapshot 的摘要覆盖严格 Candidate,包括完整经济身份与前驱五元组(排除 Repository Ref/Time/摘要自身);Repository 只允许一个 Root、以完整Ref/Schema/Digest Algorithm/Digest/State Version指向Current直接前驱的 Supersession 与 Current Pointer CAS,且整条链经济身份不变。半联合、前驱摘要/版本错配、分叉、跳号或回指旧Head必须追加SettlementInputConflictFact@1unresolved.observedInput=conflicting只绑定该Fact完整四元组,不分配Operations Case。任何 Eligibility/MeterEvent Current Head 改变都必须原子追加新 Snapshot,禁止原地拼接或自由 usageBasisSummary

Metering 提供定向、版本化的读取与验证契约:

readSettlementInput(
  settlementInputSnapshotRef, settlementInputSnapshotSchemaVersion,
  settlementInputSnapshotDigestAlgorithmVersion, settlementInputSnapshotDigest,
  expectedSettlementInputStateVersion,
  expected tenantKind, tenantId, workspaceId, projectId?,
  expected billingReservationId, billingAccountId, runId, runStepId,
  expected executionAttemptId, chargeDimensionKey,
  settlementEconomicIdentity=SettlementEconomicIdentity@1,
  readPurpose =
    { kind=billing_finalization; billingFinalizationOperationId }
    | { kind=billing_catch_up_fence; billingCatchUpValidationOperationId;
        billingCatchUpBasisDigestAlgorithmVersion; billingCatchUpBasisDigest }
    | { kind=post_finalization_transition; billingTransitionOperationId }
    | { kind=late_settlement_command; lateSettlementOperationId }
    | { kind=operations_transition_audit;
        sourceEventId / sourceEventType / sourceEventSchemaVersion;
        sourceEventEnvelopeDigestAlgorithmVersion / sourceEventEnvelopeSha256;
        billingSettlementTransitionRef / billingSettlementTransitionSchemaVersion;
        billingSettlementTransitionDigestAlgorithmVersion / billingSettlementTransitionDigest }
)
  -> { result=found; snapshot=SettlementInputSnapshot@1 }
   | { result=not_found }
   | { result=conflicting;
       settlementInputConflictFactRef / settlementInputConflictFactSchemaVersion;
       settlementInputConflictFactDigestAlgorithmVersion / settlementInputConflictFactDigest }

validateSettlementInputCurrent(
  billingReservationId, executionAttemptId, chargeDimensionKey,
  expectedSettlementEconomicIdentity=SettlementEconomicIdentity@1,
  expectedSettlementInputSnapshotRef, expectedSettlementInputSnapshotSchemaVersion,
  expectedSettlementInputSnapshotDigestAlgorithmVersion,
  expectedSettlementInputSnapshotDigest, expectedSettlementInputStateVersion,
  validationPurpose
)
  -> { result=current;
       validationReceiptRef / validationReceiptSchemaVersion;
       validationReceiptDigestAlgorithmVersion / validationReceiptDigest;
       validationPurpose;
       validatedSettlementInputSnapshotRef / validatedSettlementInputSnapshotSchemaVersion;
       validatedSettlementInputSnapshotDigestAlgorithmVersion / validatedSettlementInputSnapshotDigest;
       validatedSettlementInputStateVersion;
       validatedSettlementEconomicIdentity=SettlementEconomicIdentity@1 }
   | { result=not_current;
       currentSettlementInputSnapshotRef / currentSettlementInputSnapshotSchemaVersion;
       currentSettlementInputSnapshotDigestAlgorithmVersion / currentSettlementInputSnapshotDigest;
       currentSettlementInputStateVersion }
   | { result=conflicting;
       settlementInputConflictFactRef / settlementInputConflictFactSchemaVersion;
       settlementInputConflictFactDigestAlgorithmVersion / settlementInputConflictFactDigest }

validationPurpose =
  { kind=billing_finalization; billingFinalizationOperationId;
    finalizationInputManifestRef / finalizationInputManifestSchemaVersion;
    finalizationInputManifestDigestAlgorithmVersion / finalizationInputManifestDigest }
  | { kind=billing_catch_up_fence; billingCatchUpValidationOperationId;
      billingCatchUpBasisDigestAlgorithmVersion; billingCatchUpBasisDigest }
  | { kind=post_finalization_transition; billingTransitionOperationId;
      billingTransitionValidationOperationId;
      transitionBasisDigestAlgorithmVersion; transitionBasisDigest }
  | { kind=late_settlement_command; lateSettlementOperationId; lateSettlementCommandBasisDigestAlgorithmVersion; lateSettlementCommandBasisDigest }

readSettlementInput 可读取Current或精确历史Snapshot,但必须携带完整四元组与State Version,逐项比较Expected身份、严格经济身份、Repository不可变绑定和重算摘要;未知/错 Schema或算法、跨 Reservation/Run/Step/Attempt/Dimension、跨 Credits/Entitlement Bucket/Unit/Currency、同Ref/Schema异Digest或State Version不匹配不降格成found。前四个Purpose只接受相应Billing Finalization/Catch-up/Transition/Command Workload,并把Token Scope精确绑定到Operation、Account/Reservation/Run/Step/Attempt/Dimension、Snapshot五元组和经济身份;Catch-up还绑定Basis摘要和Fence Workload,不能被普通Transition或Admin复用。operations_transition_audit使用独立只读Audience,只允许Operations Projector在验证已接受的Settlement Transition Event时读取该Owner Fact中列出的Lineage成员;Token Scope必须同时绑定Tenant/Account/Reservation/Run/Step/Attempt/Dimension、Event ID/Type/Schema/Envelope完整摘要、Transition完整四元组和目标Snapshot五元组。Metering逐项比较受信Scope与请求,不允许Admin直接使用、跨Event复用、枚举或扩大到同Run其他Snapshot。Billing只用该读取沿Snapshot内的完整前驱五元组逐项重建有限的settlementInputLineage[],Operations只用它复核已提交Owner Fact;不存在按latest、按时间或列表枚举历史的接口。validateSettlementInputCurrent 在 Metering Repository 锁定唯一 Current Pointer后比较全部 Expected 字段并签发 Receipt。Receipt 固定 Purpose、Operation、Snapshot Ref/Schema/Digest Algorithm/Digest/State Version、完整 SettlementEconomicIdentity@1 和验证时间,使用独立 Idempotency Namespace;只接受精确 Billing Workload Audience/Reservation/Run/Step/Attempt/Dimension Scope。Billing 先构造不含 Receipt 的 Input Manifest、Catch-up Basis、Transition Basis 或 Late Command Basis,再获取 Receipt,最后将 Receipt放入对应Validation Bundle/Fence/Transition/Command执行证据,形成单向无环链。Receipt 的锁定时刻就是 Metering 输入线性化边界;其后新 Snapshot 作为下一次 post-boundary correction,不追溯污染已验证事务。Receipt 不使用隐含 TTL,也不因边界后的新 Head 失效。Finalization、Catch-up与Late Command按各自Operation/Input幂等;Catch-up Receipt只允许证明一个Fence检查时刻的Current,不能写Ledger或替代Transition Receipt。Post-finalization Transition则以 billingTransitionValidationOperationId作为Receipt唯一执行身份,并同时回显逻辑billingTransitionOperationId,相同Validation Operation/Basis/Input重放返回原Receipt,同Validation Operation异Basis/Input拒绝。同一逻辑Transition在Billing CAS失配后允许以新的From State/Basis确定性派生另一个Validation Operation;旧Receipt只能作为未采用审计证据,不能提交到新State。历史 Snapshot、错维度/经济身份、摘要篡改、未知 Purpose或越权一律拒绝。Contracts测试必须覆盖Operations Audit错误Audience、Catch-up与Transition Token/Purpose互换、跨Tenant/Event/Transition/Run/Step/Dimension/Lineage成员、Event摘要篡改与Admin Token误用,且所有拒绝都不泄露Snapshot正文。

Metering对Snapshot、前驱链、Current Pointer历史和Validation Receipt的保留期限不得短于任何引用它们的Billing Finalization/Transition/Consumption、Ledger审计、经济边界Event与Operations Rebuild证据。在线表迁移到归档后,上述精确Read仍必须通过同一Schema和授权语义返回;不得因清理中间Snapshot而使已提交Transition无法重放。保留策略变更需要先证明所有下游引用已过法务/财务保留期且不再参与重建,不能以Current Pointer仍存在替代完整Lineage。

readSettlementInputvalidateSettlementInputCurrentconflicting 只能回显与本次Expected身份全等、由Metering持久化的SettlementInputConflictFact@1完整四元组。Billing Worker把它作为Owner Conflict证据:Catch-up写Blocked Fact并保留原Active Work;Finalization则由自己的run级Case Identity协议决定是否创建Operations Case。两个Owner Read都不分配、猜测或返回reconciliationCaseRef

Billing Finalization Fence 与必达关闭事件

Finalization不是一次靠HTTP触发的临时Job。首期Execution、Metering与Billing仍是oceanway-core内共享同一PostgreSQL事务边界的逻辑模块;Core Admission在已经冻结的单一本地事务中同时创建Run Input、Run/Manifest、Billing Reservation、run.created@2.0wallet.reserved@2.0、该(billingReservationId, runId)唯一的BillingFinalizationFence@1与首代Durable Work。任何一项失败全部回滚,不引入Prepared Reservation、Abort/Timeout Saga或第二数据库;未来拆分Billing数据库必须另发ADR。Fence是Billing模块拥有的本地可变协调状态而非经济事实,至少保存Account/Reservation/Run、fenceRevision、Execution Closure Watermark、按(runStepId, executionAttemptId, chargeDimensionKey)排序的Settlement Input Watermark集合及摘要、state=waiting_for_closure|waiting_for_inputs|pending_evaluation|reconciliation_required|committed和最新Work Generation。

这些协调对象不是实现可以自行增减字段的“任务表”。Contracts必须发布以下严格Schema、联合判别、排序键、Golden JSON、Canonical Bytes与Digest向量:

BillingFinalizationFence@1 = {
  billingFinalizationFenceId / billingFinalizationFenceSchemaVersion
  tenantKind / tenantId / workspaceId / projectId?
  billingReservationId / billingAccountId / runId
  runAdmissionManifestRef / runAdmissionManifestSchemaVersion
  runAdmissionManifestDigestAlgorithmVersion / runAdmissionManifestDigest
  fenceRevision
  executionClosureWatermark =
    { state=none }
    | { state=closed;
        sourceEventId / sourceEventType / sourceEventSchemaVersion;
        sourceEventEnvelopeDigestAlgorithmVersion / sourceEventEnvelopeSha256;
        runExecutionFinalizationRef / runExecutionFinalizationSchemaVersion;
        runExecutionFinalizationDigestAlgorithmVersion / runExecutionFinalizationDigest;
        runExecutionFinalizationStateVersion;
        attemptSetManifestRef / attemptSetManifestSchemaVersion;
        attemptSetManifestDigestAlgorithmVersion / attemptSetManifestDigest;
        attemptCount }
  settlementInputWatermarkCount
  settlementInputWatermarks[] = sorted {
    runStepId / executionAttemptId / chargeDimensionKey;
    settlementEconomicIdentity = SettlementEconomicIdentity@1;
    sourceEventId / sourceEventType / sourceEventSchemaVersion;
    sourceEventEnvelopeDigestAlgorithmVersion / sourceEventEnvelopeSha256;
    currentSettlementInputSnapshotRef / currentSettlementInputSnapshotSchemaVersion;
    currentSettlementInputSnapshotDigestAlgorithmVersion / currentSettlementInputSnapshotDigest;
    currentSettlementInputStateVersion
  }
  reconciliationResolutionWatermark =
    { state=none }
    | { state=applied;
        reconciliationCaseRef / reconciliationGeneration / resolutionRevision=1;
        sourceEventId / sourceEventType / sourceEventSchemaVersion;
        sourceEventEnvelopeDigestAlgorithmVersion / sourceEventEnvelopeSha256;
        finalizationReconciliationResolutionAppliedRef / finalizationReconciliationResolutionAppliedSchemaVersion;
        finalizationReconciliationResolutionAppliedDigestAlgorithmVersion / finalizationReconciliationResolutionAppliedDigest }
  coordinationState =
    { state=waiting_for_closure; activeWork={ state=none } }
    | { state=waiting_for_inputs; activeWork={ state=none } }
    | { state=pending_evaluation;
        activeWork={ state=pending; workGeneration;
          billingFinalizationWorkRef / billingFinalizationWorkSchemaVersion;
          billingFinalizationWorkDigestAlgorithmVersion / billingFinalizationWorkDigest;
          billingFinalizationTriggerSnapshotRef / billingFinalizationTriggerSnapshotSchemaVersion;
          billingFinalizationTriggerSnapshotDigestAlgorithmVersion / billingFinalizationTriggerSnapshotDigest;
          billingFinalizationOperationId } }
    | { state=reconciliation_required; reconciliationCaseRef; reconciliationGeneration;
        expectedResolutionRevision=1;
        finalizationCaseIdentityReservationRef / finalizationCaseIdentityReservationSchemaVersion;
        finalizationCaseIdentityReservationDigestAlgorithmVersion / finalizationCaseIdentityReservationDigest;
        caseRequestEventId / caseRequestEventType / caseRequestEventSchemaVersion;
        caseRequestEventEnvelopeDigestAlgorithmVersion / caseRequestEventEnvelopeSha256;
        activeWork={ state=none } }
    | { state=committed; activeWork={ state=none };
        billingFinalizationCommittedRef / billingFinalizationCommittedSchemaVersion;
        billingFinalizationCommittedDigestAlgorithmVersion / billingFinalizationCommittedDigest }
  latestWorkGeneration
}

BillingFinalizationTriggerSnapshot@1 = {
  billingFinalizationTriggerSnapshotRef / billingFinalizationTriggerSnapshotSchemaVersion
  billingFinalizationTriggerSnapshotDigestAlgorithmVersion = jcs-sha256-v1
  billingFinalizationTriggerSnapshotDigest
  tenantKind / tenantId / workspaceId / projectId?
  billingReservationId / billingAccountId / runId
  billingFinalizationFenceId / fenceRevision / workGeneration
  runAdmissionManifestRef / runAdmissionManifestSchemaVersion
  runAdmissionManifestDigestAlgorithmVersion / runAdmissionManifestDigest
  executionClosureWatermark = BillingFinalizationFence@1.executionClosureWatermark
  settlementInputWatermarkCount
  settlementInputWatermarks[] = BillingFinalizationFence@1.settlementInputWatermarks[]
  reconciliationResolutionWatermark = BillingFinalizationFence@1.reconciliationResolutionWatermark
  createdAt
}

BillingFinalizationWork@1 = {
  billingFinalizationWorkRef / billingFinalizationWorkSchemaVersion
  billingFinalizationWorkDigestAlgorithmVersion = jcs-sha256-v1
  billingFinalizationWorkDigest
  tenantKind / tenantId / workspaceId / projectId?
  billingReservationId / billingAccountId / runId
  billingFinalizationFenceId / fenceRevision / workGeneration
  billingFinalizationTriggerSnapshotRef / billingFinalizationTriggerSnapshotSchemaVersion
  billingFinalizationTriggerSnapshotDigestAlgorithmVersion / billingFinalizationTriggerSnapshotDigest
  billingFinalizationOperationId
  createdAt
}

BillingFinalizationWorkAttemptStarted@1 = {
  billingFinalizationWorkAttemptStartedRef / billingFinalizationWorkAttemptStartedSchemaVersion
  billingFinalizationWorkAttemptStartedDigestAlgorithmVersion = jcs-sha256-v1
  billingFinalizationWorkAttemptStartedDigest
  billingFinalizationWorkRef / billingFinalizationWorkSchemaVersion
  billingFinalizationWorkDigestAlgorithmVersion / billingFinalizationWorkDigest
  billingFinalizationOperationId
  billingReservationId / billingAccountId / runId / workGeneration
  attemptNumber / fencingToken / leaseExpiresAt / startedAt
}

BillingFinalizationWorkAttemptFinished@1 = {
  billingFinalizationWorkAttemptFinishedRef / billingFinalizationWorkAttemptFinishedSchemaVersion
  billingFinalizationWorkAttemptFinishedDigestAlgorithmVersion = jcs-sha256-v1
  billingFinalizationWorkAttemptFinishedDigest
  billingFinalizationWorkRef / billingFinalizationWorkSchemaVersion
  billingFinalizationWorkDigestAlgorithmVersion / billingFinalizationWorkDigest
  billingFinalizationOperationId
  billingReservationId / billingAccountId / runId / workGeneration
  attemptNumber / fencingToken
  outcome =
    { state=blocked;
      blockingEvidence =
        { state=closure_event_not_applied }
        | { state=decision;
            billingFinalizationDecisionRef / billingFinalizationDecisionSchemaVersion;
            billingFinalizationDecisionDigestAlgorithmVersion / billingFinalizationDecisionDigest } }
    | { state=reconciliation_required; reconciliationCaseRef; reconciliationGeneration;
        finalizationCaseIdentityReservationRef / finalizationCaseIdentityReservationSchemaVersion;
        finalizationCaseIdentityReservationDigestAlgorithmVersion / finalizationCaseIdentityReservationDigest;
        caseRequestEventId / caseRequestEventType / caseRequestEventSchemaVersion;
        caseRequestEventEnvelopeDigestAlgorithmVersion / caseRequestEventEnvelopeSha256;
        billingFinalizationDecisionRef / billingFinalizationDecisionSchemaVersion;
        billingFinalizationDecisionDigestAlgorithmVersion / billingFinalizationDecisionDigest }
    | { state=committed;
        billingFinalizationCommittedRef / billingFinalizationCommittedSchemaVersion;
        billingFinalizationCommittedDigestAlgorithmVersion / billingFinalizationCommittedDigest }
    | { state=superseded; supersededByWorkGeneration }
  finishedAt
}

settlementInputWatermarks[]严格按(runStepId, executionAttemptId, chargeDimensionKey)排序、拒绝重复并与Count全等;Closure与每个Dimension Watermark只接受同一Owner Aggregate上可证明的单调后继,相同Revision异摘要进入Quarantine。Trigger Snapshot摘要覆盖除Repository-owned Ref/Time和摘要自身外的完整Candidate;Work摘要同样覆盖除Ref/Time和摘要自身外的完整Candidate,并因此绑定Trigger完整四元组与确定性Operation。Repository对(billingReservationId, runId, workGeneration)唯一且insert-or-compare,同代异Trigger/Operation冲突;waiting_for_closure|waiting_for_inputs必须且只能没有Active Work,pending_evaluation必须且只能携带唯一Pending Work,且其Generation必须等于latestWorkGeneration并与该代Work双向精确相等。数据库Check/Deferred Constraint与Contracts负向Golden必须拒绝pending_evaluation + none、等待态夹带Work、非最新代Work和终态夹带Work。latestWorkGeneration从首代1开始,只能在规范化Trigger摘要真实改变时加一,时间、重复投递与Worker重试不推进Generation。

Work正文不保存可变running/failed/retryCount。Worker领取时锁定Fence与Work,以单调fencingToken追加唯一BillingFinalizationWorkAttemptStarted@1;完成时用相同Token锁定Fence并追加唯一Finished Fact。旧Lease晚到、旧Generation或Token不等只能追加/返回superseded,不得提交Decision、Ledger、Reservation或Fence状态;重试次数和租约时长来自Operations Policy配置,不写死在契约中。Trigger Snapshot中executionClosureWatermark.state=none时,本代只能以blockingEvidence.state=closure_event_not_applied完成并回到waiting_for_closure,不得调用成功终局事务、伪造Decision/Input Manifest或用Owner Read绕过Event;该标记只绑定本代Trigger,不是Execution永久未关闭的领域事实。其他blocked必须引用严格Decision。blocked只完成这一代Work并清空activeWork,Fence返回等待态;reconciliation_requiredcommitted按严格Outcome证据推进对应状态。进程在领取前、Started后、Owner Read后或业务事务提交后崩溃,都必须由未完成Work/Attempt扫描和下述精确读取恢复,而不是创建新Operation:

readBillingFinalizationWorkByOperation(
  billingFinalizationOperationId,
  expected billingReservationId, billingAccountId, runId
)
  -> { result=found;
       work=BillingFinalizationWork@1;
       triggerSnapshot=BillingFinalizationTriggerSnapshot@1 }
   | { result=not_found }
   | { result=conflicting; conflictManifestRef / conflictManifestSchemaVersion;
       conflictManifestDigestAlgorithmVersion / conflictManifestDigest }

该Read只接受精确Billing Finalization Worker Audience/Scope,不提供列表、latest或模糊查询;返回found时Repository必须重算两个摘要、Operation派生值及Fence/Generation绑定。相同Operation只能解析到一个Work与一个Trigger;同Operation异正文、跨Account/Reservation/Run或摘要不符必须返回conflicting并停止经济写入。Operations后台只能观察、重放同一Work或按受控修复事件生成后继Generation,不能手工改Operation、Watermark或Fence终态。

Execution把Attempt集合从accepting_attempts推进到attempts_closed时,必须在同一Execution事务追加RunAttemptSetManifest@1RunExecutionFinalizationFact@1、Contracts注册的execution.run-finalization.closed@1 Event、冻结Delivery Set并创建billing_finalization_coordinator Mandatory Delivery。Event Payload封闭携带Tenant/Account/Reservation/Run、Run Admission Manifest完整四元组、Execution Finalization Fact完整四元组/State Version、Attempt Set Manifest完整四元组与Attempt Count;aggregateId=runIdaggregateRevision=1。Fact、Manifest、Event、关闭CAS或Outbox任一失败全部回滚;同Run不能有第二个Closure Event。Execution关闭后在返回响应、调用Billing或排队前崩溃,不影响该Mandatory Delivery继续投递。

Finalization Work首次判定reconciliation_required时,不能先假定Operations已有Case,也不能复用下文按Step/Attempt/Dimension定义的late_settlement身份。Billing先调用Operations的无业务副作用身份预留接口,为当前Run级对账代次取得稳定Case Ref:

FinalizationReconciliationCaseIdentityReservation@1 = {
  finalizationCaseIdentityReservationRef / finalizationCaseIdentityReservationSchemaVersion
  finalizationCaseIdentityReservationDigestAlgorithmVersion = jcs-sha256-v1
  finalizationCaseIdentityReservationDigest
  reconciliationCaseRef
  sourceOwner=billing / sourceKind=billing_finalization
  tenantKind / tenantId / workspaceId / projectId?
  billingReservationId / billingAccountId / runId / billingFinalizationFenceId
  reconciliationGeneration / caseRequestOperationId
  predecessorCase =
    { state=root }
    | { state=supersedes; previousReconciliationGeneration; previousReconciliationCaseRef;
        previousFinalizationCaseIdentityReservationRef / previousFinalizationCaseIdentityReservationSchemaVersion;
        previousFinalizationCaseIdentityReservationDigestAlgorithmVersion / previousFinalizationCaseIdentityReservationDigest }
  reservedAt
}

reserveFinalizationReconciliationCaseIdentity(
  sourceOwner=billing, sourceKind=billing_finalization,
  tenantKind, tenantId, workspaceId, projectId?,
  billingReservationId, billingAccountId, runId, billingFinalizationFenceId,
  reconciliationGeneration, caseRequestOperationId,
  predecessorCase
)
  -> { result=reserved; reservation=FinalizationReconciliationCaseIdentityReservation@1 }
   | { result=conflicting; conflictReservationRef / conflictReservationSchemaVersion;
       conflictReservationDigestAlgorithmVersion / conflictReservationDigest }

readFinalizationReconciliationCaseIdentityBySource(
  tenantKind, tenantId, workspaceId, projectId?,
  billingReservationId, billingAccountId, runId, billingFinalizationFenceId,
  reconciliationGeneration, expected caseRequestOperationId,
  predecessorCase
)
  -> { result=found; reservation=FinalizationReconciliationCaseIdentityReservation@1 }
   | { result=not_found }
   | { result=conflicting; conflictReservationRef / conflictReservationSchemaVersion;
       conflictReservationDigestAlgorithmVersion / conflictReservationDigest }

FinalizationReconciliationCaseRequestedEvent@1 = {
  tenantKind / tenantId / workspaceId / projectId?
  billingReservationId / billingAccountId / runId / billingFinalizationFenceId
  reconciliationCaseRef / reconciliationGeneration / expectedResolutionRevision=1
  caseRequestOperationId / billingFinalizationOperationId
  finalizationCaseIdentityReservationRef / finalizationCaseIdentityReservationSchemaVersion
  finalizationCaseIdentityReservationDigestAlgorithmVersion / finalizationCaseIdentityReservationDigest
  billingFinalizationDecisionRef / billingFinalizationDecisionSchemaVersion
  billingFinalizationDecisionDigestAlgorithmVersion / billingFinalizationDecisionDigest
  triggeringBillingFinalizationWorkRef / triggeringBillingFinalizationWorkSchemaVersion
  triggeringBillingFinalizationWorkDigestAlgorithmVersion / triggeringBillingFinalizationWorkDigest
  triggeringBillingFinalizationWorkGeneration
  reasonCode
}

readBillingFinalizationDecisionForOperations(
  purpose=operations_finalization_case_projection,
  caseRequestedEvent={ eventId / eventType / eventSchemaVersion;
    eventEnvelopeDigestAlgorithmVersion / eventEnvelopeSha256 },
  expected tenantKind, tenantId, workspaceId, projectId?, billingAccountId,
  billingReservationId, runId, billingFinalizationFenceId,
  reconciliationCaseRef, reconciliationGeneration, expectedResolutionRevision=1,
  caseRequestOperationId, billingFinalizationOperationId,
  finalizationCaseIdentityReservation完整四元组,
  billingFinalizationDecision完整四元组,
  triggeringBillingFinalizationWork完整四元组,
  triggeringBillingFinalizationWorkGeneration
)
  -> { result=found;
       decision=BillingFinalizationDecision@1;
       triggeringWork=BillingFinalizationWork@1 }
   | { result=not_found }
   | { result=conflicting; conflictRef / conflictSchemaVersion;
       conflictDigestAlgorithmVersion / conflictDigest }

Reservation摘要覆盖除Repository-owned Ref/Time和摘要自身外的稳定Identity Candidate,并冻结已分配Case Ref、Tenant/Workspace/Project、Run/Fence/Generation、Case Request Operation和完整前驱联合;它不绑定可能被更高Watermark抢占的Triggering Work或Billing Finalization Operation。caseRequestOperationId由Contracts固定Namespace按规范化sourceOwner/sourceKind + tenantKind/tenantId/workspaceId/projectId? + billingReservationId/billingAccountId/runId/billingFinalizationFenceId + reconciliationGeneration确定性派生;首代Generation固定为1,后继必须为前代+1并携带前代Case/Reservation完整身份。Operations按该Source Key/Generation唯一且insert-or-compare;预留只分配身份,不创建可见Case、告警、审批或业务时间线。Reserve与Identity Read只接受精确Billing Finalization Case Identity Workload Audience,Token/请求Scope必须绑定Tenant、Account/Reservation/Run/Fence/Generation、Case Request Operation与Predecessor联合;服务端重算Operation和摘要,禁止Admin调用、列表、latest、裸Ref与跨Source复用。若预留响应丢失或Billing在本地提交前崩溃,新Worker用同一Operation精确读取或重放reserve取得原Case Ref,不能生成第二身份。若G1在预留后被新Settlement Watermark产生的G2抢占,G1必须因Fence/Fencing CAS失败而零提交,G2则复用相同Identity Reservation,并只在最终获胜的Decision/CaseRequested Event中绑定自己的Work与billingFinalizationOperationId;不存在孤儿预留封死代次或为同代重分配第二Case的分支。

Billing随后在同一Finalization事务追加reconciliation_required Decision、把Fence推进到绑定该Identity Reservation的同名状态、追加Work Finished、Audit与billing.finalization-reconciliation.case-requested@1 Event,并为core_operations创建Mandatory Delivery;任一项失败全部回滚。Event以预分配reconciliationCaseRef为Aggregate、aggregateRevision=1,Payload只含上述低敏严格字段,并显式冻结最终获胜的Triggering Work完整四元组和billingFinalizationOperationId。Operations Projector先验证本地Identity Reservation,再使用Event绑定的readBillingFinalizationDecisionForOperations读取并重算Decision/Triggering Work,全部Expected身份逐项相等后才按预分配Ref幂等创建sourceKind=billing_finalization的Run级Case;它不能复用只授权Billing Worker的Work-by-operation Read,也不能盲信Event内的不透明摘要。同Event返回首次Applied Receipt,同Case/Generation异Reservation、Decision、Work或Reason冲突;Owner Fact暂不可读时保持Delivery非终态重试。Case Request提交或投影响应丢失时不得另建Case。Operations Case拥有调查、评论、指派和审批时间线,但无权修改Billing Fence、Decision、Reservation或Ledger;该代Case只有消费下述Billing-owned Resolution Applied Event后才能进入业务Resolved,后台本地“关闭”不能代替。

Finalization进入reconciliation_required后也必须存在正式恢复输入,不能靠管理员把Fence Row手工改回Pending。该入口属于ADR-031的Metering/Settlement实施门禁,不在当前ADR-030只读Explorer阶段启用;任何Producer可能把Fence推进到reconciliation_required前,必须与下列完整命令链同批上线。Operations完成调查后使用通用Admin Command Gateway,但只注册一个不会直接产生经济效果的严格请求:

FinalizationReconciliationResolutionCommandRequest@1 = {
  finalizationReconciliationResolutionCommandRequestRef
  finalizationReconciliationResolutionCommandRequestSchemaVersion
  finalizationReconciliationResolutionCommandRequestDigestAlgorithmVersion = jcs-sha256-v1
  finalizationReconciliationResolutionCommandRequestDigest
  adminCommandId / domainIdempotencyKey / actorWorkforcePrincipalId
  tenantKind / tenantId / workspaceId / projectId?
  billingAccountId / billingReservationId / runId / billingFinalizationFenceId
  reconciliationCaseRef / reconciliationGeneration
  expectedCaseRevision / expectedResolutionRevision=1
  finalizationCaseIdentityReservationRef / finalizationCaseIdentityReservationSchemaVersion
  finalizationCaseIdentityReservationDigestAlgorithmVersion / finalizationCaseIdentityReservationDigest
  caseRequestEventId / caseRequestEventType / caseRequestEventSchemaVersion
  caseRequestEventEnvelopeDigestAlgorithmVersion / caseRequestEventEnvelopeSha256
  action = reevaluate_current_owner_state
  investigationEvidenceReferenceCount
  investigationEvidenceReferences[] = sorted {
    ownerDomain=execution|metering|gateway|billing;
    evidenceKind;
    evidenceRef / evidenceSchemaVersion / evidenceDigestAlgorithmVersion / evidenceDigest }
  reasonCode
}

FinalizationReconciliationResolutionDecision@1 = {
  resolutionDecisionRef / resolutionDecisionSchemaVersion
  resolutionDecisionDigestAlgorithmVersion = jcs-sha256-v1
  resolutionDecisionDigest
  resolutionDecisionInputDigestAlgorithmVersion = jcs-sha256-v1
  resolutionDecisionInputDigest / resolutionDecisionIdempotencyKey
  finalizationReconciliationResolutionCommandRequestRef
  finalizationReconciliationResolutionCommandRequestSchemaVersion
  finalizationReconciliationResolutionCommandRequestDigestAlgorithmVersion
  finalizationReconciliationResolutionCommandRequestDigest
  adminCommandCandidateRef / adminCommandCandidateSchemaVersion
  adminCommandCandidateDigestAlgorithmVersion / adminCommandCandidateDigest
  actorWorkforcePrincipalId
  tenantKind / tenantId / workspaceId / projectId?
  billingAccountId / billingReservationId / runId / billingFinalizationFenceId
  reconciliationCaseRef / reconciliationGeneration
  expectedCaseRevision / expectedResolutionRevision=1
  finalizationCaseIdentityReservationRef / finalizationCaseIdentityReservationSchemaVersion
  finalizationCaseIdentityReservationDigestAlgorithmVersion / finalizationCaseIdentityReservationDigest
  caseRequestEventId / caseRequestEventType / caseRequestEventSchemaVersion
  caseRequestEventEnvelopeDigestAlgorithmVersion / caseRequestEventEnvelopeSha256
  action = reevaluate_current_owner_state
  investigationEvidenceReferenceCount
  investigationEvidenceReferences[] = FinalizationReconciliationResolutionCommandRequest@1.investigationEvidenceReferences[]
  decision = authorized
  decidedAt
}

Request摘要覆盖除Repository-owned Ref/Time和摘要自身外的完整严格Candidate,不包含后续Candidate、Decision、Grant、Applied Fact或resolutionOperationId;自由理由只留在Operations,跨域请求只携带注册reasonCode。Operations依次完成内容寻址Impact Preview、职责分离Approval/JIT和AdminCommandCandidate@1;Candidate固定commandType=finalization_reconciliation_resolution,其commandRequest.payload必须命中上述严格Request分支,并逐项回显Tenant、Account/Reservation/Run/Fence、Case/Generation/Revisions、Identity、Case Request Event、Action和Evidence集合,不为Candidate另造第二个同义Specialization字段。随后Operations锁定Case/Identity/Request Event,以Request+Candidate形成内容寻址Decision;Decision Input Digest排除Repository-owned Ref/Time、resolutionDecisionIdempotencyKey、最终Decision摘要及两类摘要自身字段,Idempotency Key只由Decision Schema Version与Input Digest带算法值确定性派生,最终Decision摘要再覆盖Input Digest与Key。Decision不含Grant或Operation,因此链路无环。

resolutionOperationId只由Contracts固定Namespace按完整Resolution Decision四元组确定性派生。Core Authorization随后签发SignedWorkforceCommandExecutionGrant@1的严格finalization_reconciliation_resolution分支:aud=billing_finalization_reconciliation_resolution_commandboundWorkload=发起Domain调用的Admin Command Gateway Workload Principalsub=actorWorkforcePrincipalId,并绑定Operation、Request/Candidate/Decision完整身份及上述全部业务字段;JTI只允许Billing该消费域一次使用。Billing入口必须同时验证调用方短期Workload JWT Subject等于boundWorkload,不能把Audience接收方自身误写为呈递者。Preview必须明确预测“只重开评估、无Ledger/Reservation直接变化”。Request、Candidate、Decision与Grant禁止金额、Ledger、Reservation终态、替代Snapshot、忽略冲突标志、自由Map或未注册Evidence。签发前Case/Approval/JIT变化使授权失败;签发后变化不追溯已授权的这一次命令,如需硬停止必须另发Billing-owned Revocation Fence契约。

Command Grant Exchange自身也必须可从响应丢失恢复。CommandGrantExchangeResult@1.actorAssertionIssuer + actorAssertionJti分别逐字节等于已验证Actor Assertion JWT的iss + jticommandGrantExchangeOperationId由Contracts固定Namespace只按这两个Result字段确定性派生;Candidate不进入Operation ID,而是作为不可变Grant Issuance Result中必须insert-or-compare的完整四元组。Core先认证当前Admin Command Gateway Workload并验证Assertion签名、Issuer/Audience与boundWorkload,再按Operation读取首次Result:同Assertion身份、Workload、Exchange Request Digest/Nonce和Candidate完整四元组时返回原Grant,即使Assertion已过期或JTI已消费;任一正文不同则conflicting。只有not_found才检查当前时间、Session、Approval/JIT/Case和JTI未消费,并在单一Core事务原子消费Assertion JTI、保存Issuance Result/Grant与签发Audit。唯一约束竞争必须回到首次Result读取分支,不能签发第二Grant或让已经线性化的Finalization Resolution永久卡住。Contracts与跨进程测试必须覆盖Core提交后HTTP响应丢失、跨Assertion到期重放和同JTI异Candidate冲突。

Billing验证本地Fence仍是当前Case/代次/Resolution Revision,严格解析Request、Candidate、Resolution Decision、一次性Command Grant和至少一条已登记的调查证据引用,在自己的事务追加下列事实与billing.finalization-reconciliation-resolution.applied@1 Event,并创建billing_finalization_coordinator + core_operations两条Mandatory Delivery。这些引用只证明Operations为何授权重评,不是Billing认可Owner状态已经修复的真实性凭据;Billing不按裸Ref跨域读取,真正安全结论只来自后继Finalization Work重新执行既有严格Owner Read/Validate链。

FinalizationReconciliationResolutionApplied@1 = {
  finalizationReconciliationResolutionAppliedRef / finalizationReconciliationResolutionAppliedSchemaVersion
  finalizationReconciliationResolutionAppliedDigestAlgorithmVersion = jcs-sha256-v1
  finalizationReconciliationResolutionAppliedDigest
  resolutionOperationId
  tenantKind / tenantId / workspaceId / projectId?
  billingReservationId / billingAccountId / runId / billingFinalizationFenceId
  reconciliationCaseRef / reconciliationGeneration / resolutionRevision=1
  finalizationCaseIdentityReservationRef / finalizationCaseIdentityReservationSchemaVersion
  finalizationCaseIdentityReservationDigestAlgorithmVersion / finalizationCaseIdentityReservationDigest
  caseRequestEventId / caseRequestEventType / caseRequestEventSchemaVersion
  caseRequestEventEnvelopeDigestAlgorithmVersion / caseRequestEventEnvelopeSha256
  finalizationReconciliationResolutionCommandRequestRef
  finalizationReconciliationResolutionCommandRequestSchemaVersion
  finalizationReconciliationResolutionCommandRequestDigestAlgorithmVersion
  finalizationReconciliationResolutionCommandRequestDigest
  adminCommandCandidateRef / adminCommandCandidateSchemaVersion
  adminCommandCandidateDigestAlgorithmVersion / adminCommandCandidateDigest
  resolutionDecisionRef / resolutionDecisionSchemaVersion
  resolutionDecisionDigestAlgorithmVersion / resolutionDecisionDigest
  commandExecutionGrantRef / commandExecutionGrantSchemaVersion
  commandExecutionGrantDigestAlgorithmVersion / commandExecutionGrantDigest / commandExecutionGrantJti
  action = reevaluate_current_owner_state
  investigationEvidenceReferenceCount
  investigationEvidenceReferences[] = sorted {
    ownerDomain=execution|metering|gateway|billing;
    evidenceKind;
    evidenceRef / evidenceSchemaVersion / evidenceDigestAlgorithmVersion / evidenceDigest }
  actorWorkforcePrincipalId
  appliedAt
}

FinalizationReconciliationResolutionAppliedEvent@1 = {
  tenantKind / tenantId / workspaceId / projectId?
  billingReservationId / billingAccountId / runId / billingFinalizationFenceId
  reconciliationCaseRef / reconciliationGeneration / resolutionRevision=1 / resolutionOperationId
  finalizationCaseIdentityReservationRef / finalizationCaseIdentityReservationSchemaVersion
  finalizationCaseIdentityReservationDigestAlgorithmVersion / finalizationCaseIdentityReservationDigest
  finalizationReconciliationResolutionAppliedRef / finalizationReconciliationResolutionAppliedSchemaVersion
  finalizationReconciliationResolutionAppliedDigestAlgorithmVersion / finalizationReconciliationResolutionAppliedDigest
  action = reevaluate_current_owner_state
}

readFinalizationReconciliationResolutionAppliedForOperations(
  purpose=operations_finalization_case_projection,
  resolutionEvent={ eventId / eventType / eventSchemaVersion;
    eventEnvelopeDigestAlgorithmVersion / eventEnvelopeSha256 },
  expected tenantKind, tenantId, workspaceId, projectId?, billingAccountId,
  billingReservationId, runId, billingFinalizationFenceId,
  reconciliationCaseRef, reconciliationGeneration, resolutionRevision=1,
  resolutionOperationId, actorWorkforcePrincipalId,
  finalizationCaseIdentityReservation完整四元组,
  finalizationReconciliationResolutionApplied完整四元组
)
  -> { result=found; fact=FinalizationReconciliationResolutionApplied@1 }
   | { result=not_found }
   | { result=conflicting; conflictRef / conflictSchemaVersion;
       conflictDigestAlgorithmVersion / conflictDigest }

readFinalizationReconciliationResolutionAppliedByOperation(
  resolutionOperationId,
  expected tenantKind, tenantId, workspaceId, projectId?,
  expected billingAccountId, billingReservationId, runId,
  billingFinalizationFenceId, reconciliationCaseRef,
  reconciliationGeneration, resolutionRevision=1, actorWorkforcePrincipalId,
  finalizationCaseIdentityReservation完整四元组,
  finalizationReconciliationResolutionCommandRequest完整四元组,
  adminCommandCandidate完整四元组,
  finalizationReconciliationResolutionDecision完整四元组,
  signedWorkforceCommandExecutionGrant完整四元组 / commandGrantJti
)
  -> { result=found; fact=FinalizationReconciliationResolutionApplied@1 }
   | { result=not_found }
   | { result=conflicting; conflictRef / conflictSchemaVersion;
       conflictDigestAlgorithmVersion / conflictDigest }

Fact摘要覆盖除Repository-owned Ref/Time和摘要自身外的完整Candidate;Case Ref/Generation必须与Identity Reservation、Case Request Event、当前Fence和Operations Resolution Decision逐项相等。每个Reconciliation Generation只允许一次Resolution Applied,因此Case Requested固定expectedResolutionRevision=1,Request/Decision/Grant/Applied Fact/Event全部固定resolutionRevision=1;若重评仍冲突必须创建下一Case Generation,不能在同一代递增Resolution Revision。Request、Candidate、Decision、Grant与Applied Fact的Tenant、Account/Reservation/Run/Fence、Case/Generation/Revisions、Identity、Case Request Event、Action、调查证据引用集合及Actor必须逐项相等;任一裁剪、夹带或跨对象重绑都失败。调查引用按(ownerDomain, evidenceKind, evidenceRef, evidenceSchemaVersion)排序、拒绝重复并与Count全等,evidenceKind必须解析到Contracts注册的具体不可变Schema,但Billing不以该列表执行泛化Owner Read或声明修复成功;后继重评若仍读到冲突,必须进入新Case代次。actorWorkforcePrincipalId必须与Resolution Decision Actor、Command Grant sub、执行命令Candidate Actor、Audit Actor及Event Envelope Actor逐项相等,跨Actor重放一律拒绝。resolutionOperationId必须按Resolution Decision完整四元组确定性复算;Grant JTI与(billingReservationId, reconciliationGeneration, resolutionRevision=1)唯一消费。Resolution命令入口必须先以readFinalizationReconciliationResolutionAppliedByOperation执行本地结果恢复:found时完整比较原Request/Candidate/Decision/Grant并返回首次Fact,即使新的HTTP重投已跨过Grant有效期或JTI已经消费也不能重复拒绝已提交结果;只有not_found才进入当前Grant时间/JTI验证与写事务,conflicting立即停写。Operations Projector则必须用Event绑定的readFinalizationReconciliationResolutionAppliedForOperations重算Fact摘要和Case Request/Request/Candidate/Decision/Grant/Actor绑定后才终结Case;两个Read具有不同Audience/Scope,不提供列表、latest或裸Ref。该Fact本身就是稳定Command Result,不再另造可与它分叉的结果对象。该命令只能请求重新评估:禁止携带金额、Ledger、Reservation终态、替代Snapshot或“忽略冲突”标志,也不能覆盖Execution/Metering/Gateway事实。Event的aggregateId由Contracts按billingFinalizationFenceId + reconciliationGeneration确定性派生,aggregateRevision=1;每个Aggregate只允许这一个Applied Event。同一Fence的新Case代次产生新Aggregate ID,不需要跨代连续Revision。Event同时冻结billing_finalization_coordinatorcore_operations两个Mandatory Destination,前者重开评估、后者仅在核验Billing Fact后关闭当前Operations Case。Fact、Grant消费、Audit、Event/Delivery任一失败全部回滚。

Coordinator收到当前代Resolution Event时,在单一Inbox事务锁定Fence,验证它仍为相同Case/Generation且resolutionRevision=expectedResolutionRevision,单调推进reconciliationResolutionWatermark,把状态改为pending_evaluation并把Case期间已耐久接受的最新Closure/Settlement Watermark与Resolution Watermark共同纳入规范Trigger,生成新Work Generation,最后与Consumer Receipt一起Ack。后继评估若仍冲突,必须创建新的reconciliationGeneration及Case并等待新的Resolution;旧Case Grant/Decision/Event不能复用。Fence处于reconciliation_required期间,新到Settlement Owner Event只更新Watermark并返回held_for_resolution,禁止自行生成Work或吸收Case;Execution Closure在进入该状态前已经唯一且不可逆地应用,重投只返回首次Receipt,不需要第二种Held分支。只有当前代Resolution Event可以离开该状态;已经被更早同代Resolution合法应用,或当前Case/Generation已由该次重评进入后继代时,晚到/重复Resolution才可用严格already_superseded Receipt绑定Event和当前Fence Revision后Ack而不创建第二Work。同Revision异摘要、跨Case/代次或Grant/Fact不等进入Quarantine且不伪造Ack。Retention不得删除仍被Fence/Trigger/Work/Receipt引用的Resolution Fact/Event。Contracts与跨进程测试必须覆盖Resolution Event提交后响应丢失、Ack前后崩溃、Settlement更新先到但仅推进Watermark、旧代Event晚到、同Revision异摘要、重评再次冲突和旧Grant跨代重放。

Billing的Closure Event Consumer与前述Settlement Input Event Consumer使用同一个Finalization Fence协议:严格验证Envelope/Payload后,在单一Inbox事务锁定Fence,单调推进Closure或Dimension Watermark;若Reservation尚未成功终局、Fence不在reconciliation_required且触发快照发生变化,则递增fenceRevision/workGeneration、内容寻址保存规范排序的BillingFinalizationTriggerSnapshot@1并insert-or-compare新BillingFinalizationWork@1,最后才与Consumer Receipt一起Ack。Reconciliation期间,新到Settlement Event只原子推进Watermark并写held_for_resolution Receipt,Resolution Event稍后一次性纳入所有新输入;Closure Event在能够进入Reconciliation前已经应用且只有一个不可逆Revision,其重投返回首次Receipt。普通早到Settlement Input不能只停在无记录的awaiting_finalization:它必须推进Finalization Fence并产生/合并Pending Work;早到Closure同理。相同Event同摘要不增加Generation,同Revision异摘要隔离。成功终局强制要求Closure Event已经由该Consumer应用,因此不存在“Reservation先Committed、Closure Delivery后到且无Work可建”的分支;Event尚未应用时任何Work都只能等待。即使事件在Fence初建事务前异常到达,Consumer也只能先耐久保存Inbox Watermark并重试Fence绑定,不能丢弃。

每个Work的billingFinalizationOperationId由Contracts固定Namespace按billingReservationId + runId + workGeneration + BillingFinalizationTriggerSnapshot完整摘要确定性派生,并与Work在同一Billing事务保存;时间、Worker ID、投递Attempt和随机数都不参与。Worker调用readRunFinalizationPrecondition、构造Input Manifest和获取Receipts时只能使用该Operation。响应丢失或Worker重启按Work唯一键恢复同一Operation;Fence不在reconciliation_required时,Closure/Dimension Watermark任一真实推进会创建新Generation和新Operation,不会让旧Operation绑定不同输入;处于Reconciliation时只推进Watermark并等待当前代Resolution Applied Event统一创建后继Generation。

一代Work得到blocked只表示该Trigger Snapshot尚未满足Closure或输入封闭条件:可以追加对应Decision/Audit并把该Work标记完成,但Finalization Fence必须回到waiting_for_closure|waiting_for_inputs,不得进入终态。reconciliation_required把Fence置为同名非终态并关联Case;只有Contracts注册的Resolution Applied Event才能生成后继Work,调查引用本身或Owner状态变化都只能等待该入口。只有成功settle_and_release_remainder|release_all事务才能把Fence与Reservation一起原子推进为committed;同事务还必须创建每维度Catch-up Fence/Work,以承接Receipt之后的变化。自动重评因此至少覆盖:先blocked后Input已决、Input先到后Execution关闭、Closure先到后Input封闭、任一Consumer Ack后Worker崩溃,以及Closure/最后一个Input与旧Work并发。测试必须证明blocked之后没有人工HTTP调用也能由Mandatory Event收敛,且不存在Reservation已终局而Finalization Fence未Committed或反向裂缝。

Reservation 的结算与余额释放需要版本化终局证据,不能因为“当前尚未看到 MeterEvent”立即断言永远无费用,也不能看到第一条 MeterEvent 就忽略其他 Attempt/Charge Dimension。Execution 在业务生命周期确认该 Run 不再允许任何重试、故障切换或新执行后,先在自己的 Repository 内以 CAS 将 Attempt Admission 从 accepting_attempts 不可逆推进为 attempts_closed,并按上节在同一事务追加内容寻址的 RunAttemptSetManifest@1 + RunExecutionFinalizationFact@1 + execution.run-finalization.closed@1及Mandatory Delivery。这一步只关闭执行集合并耐久唤醒Billing,不表示 Billing 已成功结算;Billing 失败、输入未决或对账都只能重试结算或修正 Evidence,不得重新打开原 Run。若仍需真实执行,必须走新的 Run 与新的预算准入。

RunAttemptSetManifest@1 = {
  attemptSetManifestRef / attemptSetManifestSchemaVersion
  attemptSetManifestDigestAlgorithmVersion = jcs-sha256-v1
  attemptSetManifestDigest
  runId / attemptCount
  attempts[] = sorted {
    runStepId / executionAttemptId;
    attemptExecutionManifestRef / attemptExecutionManifestSchemaVersion;
    attemptExecutionManifestDigestAlgorithmVersion / attemptExecutionManifestDigest;
    executionEligibilityFactRef / executionEligibilityFactSchemaVersion;
    executionEligibilityFactDigestAlgorithmVersion / executionEligibilityFactDigest
  }
}

RunExecutionFinalizationConflictFact@1 = {
  runExecutionFinalizationConflictFactRef / runExecutionFinalizationConflictFactSchemaVersion
  runExecutionFinalizationConflictFactDigestAlgorithmVersion = jcs-sha256-v1
  runExecutionFinalizationConflictFactDigest
  runId / billingFinalizationOperationId
  expectedFinalization =
    { state=unknown }
    | { state=identified;
        runExecutionFinalizationRef / runExecutionFinalizationSchemaVersion;
        runExecutionFinalizationDigestAlgorithmVersion / runExecutionFinalizationDigest;
        stateVersion;
        attemptSetManifestRef / attemptSetManifestSchemaVersion;
        attemptSetManifestDigestAlgorithmVersion / attemptSetManifestDigest }
  conflictKind = repository_binding | canonical_digest | attempt_set | eligibility_set | identity_scope
  reasonCode
  detectedAt
}

RunExecutionFinalizationFact@1 = {
  runExecutionFinalizationRef / runExecutionFinalizationSchemaVersion
  runExecutionFinalizationDigestAlgorithmVersion = jcs-sha256-v1
  runExecutionFinalizationDigest / stateVersion
  runId / closureOperationId / closedAt
  attemptAdmissionState = attempts_closed
  attemptSetManifestRef / attemptSetManifestSchemaVersion
  attemptSetManifestDigestAlgorithmVersion / attemptSetManifestDigest
  attemptCount
  attempts[] = sorted {
    runStepId / executionAttemptId;
    attemptExecutionManifestRef / attemptExecutionManifestSchemaVersion;
    attemptExecutionManifestDigestAlgorithmVersion / attemptExecutionManifestDigest;
    executionEligibilityFactRef / executionEligibilityFactSchemaVersion;
    executionEligibilityFactDigestAlgorithmVersion / executionEligibilityFactDigest
  }
}

Manifest 与 Fact 分别以 UTF-8 NFC、严格 Schema、RFC 8785 JCS 与 SHA-256 覆盖除 Repository-owned Ref/Time和各自摘要字段外的完整 Candidate;Contracts 发布两者 Golden。RunAttemptSetManifest@1 与 Fact 的 attempts[](runStepId, executionAttemptId)排序并逐项、逐序相等,Step/Attempt还必须与各自Attempt Manifest和Execution Eligibility Fact全等;它们覆盖该 Run 在关闭事务中锁定的全部 Attempt及每个 Attempt唯一资格事实,不允许从Attempt反查Step。空集合也必须以显式 attemptCount=0 和空数组表达。Fact stateVersion 首期固定为 1。Execution Repository 以 Run 唯一约束、状态 CAS、Attempt/Eligibility Fact 外键和同事务 Manifest/Fact append 拒绝漏项、重复、乱序、错Step、第二个关闭事实与关闭后新增 Attempt;关闭后没有 reopen 或 supersede 分支。

Execution 提供定向的版本化读取与验证契约,Billing 不读取 Execution 数据库:

readRunFinalizationPrecondition(
  runId, billingFinalizationOperationId
)
  -> { result=not_closed; observedAttemptAdmissionState=accepting_attempts;
       observedExecutionStateRevision;
       preconditionReadReceiptRef / preconditionReadReceiptSchemaVersion;
       preconditionReadReceiptDigestAlgorithmVersion / preconditionReadReceiptDigest }
   | { result=closed;
       runExecutionFinalizationRef / runExecutionFinalizationSchemaVersion;
       runExecutionFinalizationDigestAlgorithmVersion / runExecutionFinalizationDigest;
       runExecutionFinalizationStateVersion;
       attemptSetManifestRef / attemptSetManifestSchemaVersion;
       attemptSetManifestDigestAlgorithmVersion / attemptSetManifestDigest;
       preconditionReadReceiptRef / preconditionReadReceiptSchemaVersion;
       preconditionReadReceiptDigestAlgorithmVersion / preconditionReadReceiptDigest }
   | { result=run_not_found;
       preconditionReadReceiptRef / preconditionReadReceiptSchemaVersion;
       preconditionReadReceiptDigestAlgorithmVersion / preconditionReadReceiptDigest }
   | { result=conflicting;
       runExecutionFinalizationConflictFactRef / runExecutionFinalizationConflictFactSchemaVersion;
       runExecutionFinalizationConflictFactDigestAlgorithmVersion / runExecutionFinalizationConflictFactDigest;
       preconditionReadReceiptRef / preconditionReadReceiptSchemaVersion;
       preconditionReadReceiptDigestAlgorithmVersion / preconditionReadReceiptDigest }

readRunExecutionFinalization(
  runExecutionFinalizationRef, runExecutionFinalizationSchemaVersion,
  expected runId
)
  -> { result=found; fact=RunExecutionFinalizationFact@1 }
   | { result=not_found }
   | { result=conflicting;
       runExecutionFinalizationConflictFactRef / runExecutionFinalizationConflictFactSchemaVersion;
       runExecutionFinalizationConflictFactDigestAlgorithmVersion / runExecutionFinalizationConflictFactDigest }

validateRunExecutionFinalizationCurrent(
  runId,
  expectedRunExecutionFinalizationRef, expectedRunExecutionFinalizationSchemaVersion,
  expectedRunExecutionFinalizationDigestAlgorithmVersion,
  expectedRunExecutionFinalizationDigest, expectedStateVersion,
  expectedAttemptSetManifestRef, expectedAttemptSetManifestSchemaVersion,
  expectedAttemptSetManifestDigestAlgorithmVersion, expectedAttemptSetManifestDigest,
  billingFinalizationOperationId,
  finalizationInputManifestRef, finalizationInputManifestSchemaVersion,
  finalizationInputManifestDigestAlgorithmVersion, finalizationInputManifestDigest
)
  -> { result=current;
       validationReceiptRef / validationReceiptSchemaVersion;
       validationReceiptDigestAlgorithmVersion / validationReceiptDigest;
       billingFinalizationOperationId;
       validatedRunExecutionFinalizationRef / validatedRunExecutionFinalizationSchemaVersion;
       validatedRunExecutionFinalizationDigestAlgorithmVersion / validatedRunExecutionFinalizationDigest;
       validatedStateVersion;
       validatedAttemptSetManifestRef / validatedAttemptSetManifestSchemaVersion;
       validatedAttemptSetManifestDigestAlgorithmVersion / validatedAttemptSetManifestDigest;
       validatedFinalizationInputManifestRef / validatedFinalizationInputManifestSchemaVersion;
       validatedFinalizationInputManifestDigestAlgorithmVersion / validatedFinalizationInputManifestDigest }
   | { result=not_found }
   | { result=conflicting;
       runExecutionFinalizationConflictFactRef / runExecutionFinalizationConflictFactSchemaVersion;
       runExecutionFinalizationConflictFactDigestAlgorithmVersion / runExecutionFinalizationConflictFactDigest }

readRunFinalizationPrecondition 是尚未知关闭 Ref 时的唯一Owner入口,但Finalization Worker只有在其持久Trigger Snapshot已包含Billing Inbox接受的Closure Watermark后才允许调用;它不能让Admission首代Work越过尚未应用的Mandatory Event。该接口只接受 Billing Finalization Workload、精确 Run/Operation Audience 与 Scope;billingFinalizationOperationId必须来自已持久化BillingFinalizationWork@1并由其Generation/Trigger Snapshot摘要确定性复算,调用方不得随机生成或在重试时换值。Execution 在锁定 Run 的本地读取边界后追加内容寻址的 Precondition Read Receipt,Receipt 摘要绑定 Operation、Run、严格结果联合、Observed State Revision 与 Closed 时的完整 Finalization/Attempt Set 四元组。相同 Run/Operation/Result 幂等返回原 Receipt,同 Operation 异结果或身份冲突;Closure/Settlement Watermark变化后的新评估使用新Work Generation派生的新Operation。Billing 对 closed 结果必须要求返回的Finalization/Attempt Set四元组、State Version与Attempt Count逐项等于Trigger中的Closure Event Watermark,再继续精确读取、构造Input Manifest;不等则Reconciliation,且Precondition Receipt不能当作成功终局Receipt。

精确 Read 与 Current Validation 同样只接受 Billing Finalization Workload、精确 Run/Operation Audience 与 Scope。Current Receipt 永久固定不可逆关闭事实、Attempt Set 和已知 Input Manifest 四元组,不使用隐含 TTL;相同 Operation/Input/Fact 幂等返回原 Receipt,同 Operation 异 Input、Fact/Manifest 摘要错配、Attempt Set 不等或越权必须拒绝。由于关闭事实本身不可变,Receipt 后不存在“新 Attempt 使其过期”的分支。

Billing 每次完成一次严格输入评估都可追加不可变 BillingFinalizationDecision,但必须区分“尚无关闭集合”和“已有关闭集合”两类输入。Execution 尚未关闭时不能伪造 FinalizationInputManifest;Billing 只保存下面的 BillingFinalizationPreconditionEvaluation@1 并写 blocked。Execution 返回不存在或冲突时保存同类评估并写 reconciliation_required。这两类分支只记录当次受信 Read Contract 结果,不写 Ledger、不终结 Reservation,也不能作为未来成功终局的验证证据。Execution 可以在某次 blocked 评估之后独立关闭 Attempt;一旦关闭,即使后续评估仍 blocked/reconciliation,也不得重开。

BillingFinalizationPreconditionEvaluation@1 = {
  finalizationPreconditionEvaluationRef / finalizationPreconditionEvaluationSchemaVersion
  finalizationPreconditionEvaluationDigestAlgorithmVersion = jcs-sha256-v1
  finalizationPreconditionEvaluationDigest
  billingFinalizationOperationId
  billingReservationId / runId
  billingPolicyRevisionId / evidenceFinalizationRuleVersion
  executionReadContractVersion
  executionPrecondition =
    { state=not_closed; observedAttemptAdmissionState=accepting_attempts;
      observedExecutionStateRevision;
      preconditionReadReceiptRef / preconditionReadReceiptSchemaVersion;
      preconditionReadReceiptDigestAlgorithmVersion / preconditionReadReceiptDigest }
    | { state=run_not_found;
      preconditionReadReceiptRef / preconditionReadReceiptSchemaVersion;
      preconditionReadReceiptDigestAlgorithmVersion / preconditionReadReceiptDigest }
    | { state=conflicting;
        runExecutionFinalizationConflictFactRef / runExecutionFinalizationConflictFactSchemaVersion;
        runExecutionFinalizationConflictFactDigestAlgorithmVersion / runExecutionFinalizationConflictFactDigest;
        preconditionReadReceiptRef / preconditionReadReceiptSchemaVersion;
        preconditionReadReceiptDigestAlgorithmVersion / preconditionReadReceiptDigest }
  reasonCode
  recordedAt
}

Precondition Evaluation Digest 覆盖除 Repository-owned Ref/Time和摘要自身外的完整严格 Candidate;同 Operation insert-or-compare,且必须验证嵌套 Receipt 的 Purpose/Run/Operation/Result/State 与 Evaluation 逐项相等,Contracts 发布三个分支与摘要 Golden。它只证明 Billing 在该 Operation 观察到的前置结果;not_closed/run_not_found 不是永久负面事实,后续必须用新 Operation 重新读取。只有 Execution 已关闭、本代Trigger的Closure Watermark已经接受且与Owner读取逐项相等,Billing 才能构造 FinalizationInputManifest,并在计量集合未封闭时写 blocked、输入冲突/超预占/存在未决 Case时写 reconciliation_required。只有冻结的 billingPolicyRevisionId 所定义 Evidence Finalization Rule 已满足、关闭事实中的完整 Attempt×Charge Dimension 集合已封闭、每个 Execution Eligibility 已形成终态、没有 Pending/Conflicting Usage Availability 且无未决 Reconciliation 时,才允许 settle_and_release_remainder | release_all:目标为零时释放全部预占,目标为正且不超过可结算预占时结算所有已验证 Head 并释放余量。成功 Billing 终局后绝不重开、改写或重新预占原 Reservation。

BillingFinalizationDecision 是 Billing Repository 的严格追加事实,而不是一次“查询为空”的布尔判断:

billingFinalizationDecisionRef / billingFinalizationDecisionSchemaVersion
billingFinalizationDecisionDigestAlgorithmVersion = jcs-sha256-v1
billingFinalizationDecisionDigest
billingFinalizationDecisionIdempotencyKey
billingReservationId / runId
billingPolicyRevisionId / evidenceFinalizationRuleVersion
finalizationInput =
  { state=precondition_evaluation;
    finalizationPreconditionEvaluationRef / finalizationPreconditionEvaluationSchemaVersion;
    finalizationPreconditionEvaluationDigestAlgorithmVersion / finalizationPreconditionEvaluationDigest }
  | { state=closed_attempt_set;
    finalizationInputManifestRef / finalizationInputManifestSchemaVersion;
    finalizationInputManifestDigestAlgorithmVersion / finalizationInputManifestDigest }
finalizationValidation =
  { state=available; finalizationValidationBundleRef; finalizationValidationBundleSchemaVersion;
    finalizationValidationBundleDigestAlgorithmVersion; finalizationValidationBundleDigest }
  | { state=none; reasonCode }
inputDigestAlgorithmVersion = jcs-sha256-v1
inputDigest
status = settle_and_release_remainder | release_all | blocked | reconciliation_required
reconciliationCase =
  { state=none }
  | { state=required; reconciliationCaseRef / reconciliationGeneration / expectedResolutionRevision=1;
      caseRequestOperationId;
      finalizationCaseIdentityReservationRef / finalizationCaseIdentityReservationSchemaVersion;
      finalizationCaseIdentityReservationDigestAlgorithmVersion / finalizationCaseIdentityReservationDigest }
runExecutionFinalization =
  { state=available;
    runExecutionFinalizationRef / runExecutionFinalizationSchemaVersion;
    runExecutionFinalizationDigestAlgorithmVersion / runExecutionFinalizationDigest;
    runExecutionFinalizationStateVersion;
    attemptSetManifestRef / attemptSetManifestSchemaVersion;
    attemptSetManifestDigestAlgorithmVersion / attemptSetManifestDigest }
  | { state=missing }
  | { state=conflicting;
      runExecutionFinalizationConflictFactRef / runExecutionFinalizationConflictFactSchemaVersion;
      runExecutionFinalizationConflictFactDigestAlgorithmVersion / runExecutionFinalizationConflictFactDigest }
reasonCode  # Contracts 注册枚举
supersedesFinalizationDecisionRef? / supersedesFinalizationDecisionSchemaVersion?
supersedesFinalizationDecisionDigestAlgorithmVersion? / supersedesFinalizationDecisionDigest?
decidedAt

finalizationInputrunExecutionFinalization 有双向 Refinement:precondition_evaluation 只允许 runExecutionFinalization.state=missing|conflicting + finalizationValidation.state=none + status=blocked|reconciliation_required,并要求 Decision 与 Precondition Evaluation 的 Reservation/Run/Policy/Rule/Operation/Reason 和 Execution 结果逐项相等;closed_attempt_set 只允许 runExecutionFinalization.state=available,且其关闭事实/Attempt Set必须与 Manifest 完全一致。成功状态进一步强制 finalizationValidation.state=available;关闭集合下的 blocked/reconciliation 可以为 none,但不能使用 Precondition Evaluation 代替已经存在的 Manifest。status=reconciliation_required必须且只能携带reconciliationCase.state=required,完整绑定当前代Identity Reservation;其他三个Status必须state=none。未知组合、两分支字段混合、Reconciliation裸Case Ref或Precondition分支尝试写 Ledger 一律拒绝。

FinalizationInputManifest@1FinalizationValidationBundle@1 是 Contracts 可直接生成运行时 Schema/TypeScript/JSON Schema 的封闭 DTO,不是实现自行解释的 Map:

FinalizationInputManifest@1 = {
  finalizationInputManifestRef / finalizationInputManifestSchemaVersion
  finalizationInputManifestDigestAlgorithmVersion = jcs-sha256-v1
  finalizationInputManifestDigest
  billingFinalizationOperationId
  billingReservationId / billingAccountId / runId
  billingPolicyRevisionId / evidenceFinalizationRuleVersion
  reservedValue =
    { kind=credits; amount }
    | { kind=entitlement; entitlementKey; quantity; unit }
    | { kind=money; money={ amount; currency } }
  settlementEconomicIdentity = SettlementEconomicIdentity@1
  executionClosure = {
    runExecutionFinalizationRef / runExecutionFinalizationSchemaVersion;
    runExecutionFinalizationDigestAlgorithmVersion / runExecutionFinalizationDigest;
    runExecutionFinalizationStateVersion;
    attemptSetManifestRef / attemptSetManifestSchemaVersion;
    attemptSetManifestDigestAlgorithmVersion / attemptSetManifestDigest
  }
  attemptCount / chargeDimensionCount
  attempts[] = sorted {
    runStepId / executionAttemptId;
    attemptExecutionManifestRef / attemptExecutionManifestSchemaVersion;
    attemptExecutionManifestDigestAlgorithmVersion / attemptExecutionManifestDigest;
    executionEligibilityFactRef / executionEligibilityFactSchemaVersion;
    executionEligibilityFactDigestAlgorithmVersion / executionEligibilityFactDigest;
    chargeDimensionSetDefinitionRef / chargeDimensionSetDefinitionSchemaVersion;
    chargeDimensionSetDefinitionDigestAlgorithmVersion / chargeDimensionSetDefinitionDigest;
    dimensionCount;
    dimensions[] = sorted {
      chargeDimensionKey / chargeType / chargeTypeRegistryVersion / unit;
      settlementEconomicIdentity=SettlementEconomicIdentity@1;
      settlementInputSnapshotRef / settlementInputSnapshotSchemaVersion;
      settlementInputSnapshotDigestAlgorithmVersion / settlementInputSnapshotDigest / settlementInputStateVersion;
      settlementInputResolution=SettlementInputSnapshot@1.resolution;
      billingEvaluation =
        { state=resolved; pricingSnapshotId; billingPolicyRevisionId;
          pricingCalculatorVersion;
          pricingInputDigestAlgorithmVersion / pricingInputDigest;
          targetAmountDecimal; alreadySettledAmountDecimal; pendingDeltaAmountDecimal }
        | { state=unresolved; reasonCode;
            ownerConflictFactCount;
            ownerConflictFacts[]=sorted {
              ownerDomain=execution|metering|gateway;
              factRef / factSchemaVersion / factDigestAlgorithmVersion / factDigest } }
    }
  }
  totals = {
    settlementEconomicIdentity=SettlementEconomicIdentity@1;
    reservedAmountDecimal; targetNetAmountDecimal; alreadySettledNetAmountDecimal;
    pendingDeltaAmountDecimal; releaseAmountDecimal
  }
  createdAt
}

FinalizationValidationBundle@1 = {
  finalizationValidationBundleRef / finalizationValidationBundleSchemaVersion
  finalizationValidationBundleDigestAlgorithmVersion = jcs-sha256-v1
  finalizationValidationBundleDigest
  billingFinalizationOperationId
  billingReservationId / billingAccountId / runId
  finalizationInputManifestRef / finalizationInputManifestSchemaVersion
  finalizationInputManifestDigestAlgorithmVersion / finalizationInputManifestDigest
  executionFinalizationReceipt = {
    validationReceiptRef / validationReceiptSchemaVersion;
    validationReceiptDigestAlgorithmVersion / validationReceiptDigest;
    billingFinalizationOperationId;
    validatedRunExecutionFinalizationRef / validatedRunExecutionFinalizationSchemaVersion;
    validatedRunExecutionFinalizationDigestAlgorithmVersion / validatedRunExecutionFinalizationDigest;
    validatedRunExecutionFinalizationStateVersion;
    validatedAttemptSetManifestRef / validatedAttemptSetManifestSchemaVersion;
    validatedAttemptSetManifestDigestAlgorithmVersion / validatedAttemptSetManifestDigest;
    validatedFinalizationInputManifestRef / validatedFinalizationInputManifestSchemaVersion;
    validatedFinalizationInputManifestDigestAlgorithmVersion / validatedFinalizationInputManifestDigest
  }
  meteringReceiptCount
  meteringSettlementInputReceipts[] = sorted {
    runStepId / executionAttemptId / chargeDimensionKey;
    settlementEconomicIdentity=SettlementEconomicIdentity@1;
    settlementInputSnapshotRef / settlementInputSnapshotSchemaVersion;
    settlementInputSnapshotDigestAlgorithmVersion / settlementInputSnapshotDigest / settlementInputStateVersion;
    validationPurpose = { kind=billing_finalization; billingFinalizationOperationId;
      finalizationInputManifestRef / finalizationInputManifestSchemaVersion;
      finalizationInputManifestDigestAlgorithmVersion / finalizationInputManifestDigest };
    validationReceiptRef / validationReceiptSchemaVersion;
    validationReceiptDigestAlgorithmVersion / validationReceiptDigest
  }
  gatewayReceiptCount
  gatewayAvailabilityReceipts[] = sorted {
    runStepId / executionAttemptId / gatewayDeploymentId;
    attemptRouteBindingRef / attemptRouteBindingSchemaVersion;
    attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest;
    gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion;
    gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest;
    providerEvidenceKind=provider_usage / evidenceDimensionKey=attempt_aggregate@1;
    availabilitySnapshotRef / availabilitySnapshotSchemaVersion;
    availabilitySnapshotDigestAlgorithmVersion / availabilitySnapshotDigest / availabilityStateVersion;
    validatedEvidence =
      { state=none }
      | { state=available; providerUsageEvidenceRef / providerUsageEvidenceSchemaVersion;
          providerUsageEvidenceDigestAlgorithmVersion / providerUsageEvidenceDigest };
    validationPurpose = { kind=billing_finalization; billingFinalizationOperationId;
      finalizationInputManifestRef / finalizationInputManifestSchemaVersion;
      finalizationInputManifestDigestAlgorithmVersion / finalizationInputManifestDigest };
    validationReceiptRef / validationReceiptSchemaVersion;
    validationReceiptDigestAlgorithmVersion / validationReceiptDigest
  }
  createdAt
}

Manifest 的 reservedValue 必须逐项等于 Run Admission/Wallet Reservation 的严格 BillingValue;顶层 settlementEconomicIdentity 是去除数额后的确定性投影,且必须和每个 Dimension、Snapshot、Billing Evaluation与 Totals 相等。一个 Reservation 首期只允许一种经济身份,不能跨 Credits、Entitlement Bucket/Unit 或 Money Currency 汇总。ChargeDimensionSetDefinition 是 Billing Policy/Offering 在准入时冻结的内容寻址严格定义;每个 Attempt 的 dimensions[] 必须与其完整定义集合精确等值。attempts[](runStepId, executionAttemptId) 排序,必须与关闭事实的完整 Attempt 集逐项相等,并与Attempt Manifest、Execution Eligibility Fact和每个Dimension Snapshot的Step/Attempt全等;dimensions[]chargeDimensionKey 排序,跨 Attempt 的 Dimension 总数必须等于 chargeDimensionCount。重复 Key、漏项、夹带、错Step、Count 不等或数组未按 Contracts 顺序都拒绝。

settlementInputResolution 必须逐字段复制被引用 SettlementInputSnapshot@1.resolution 的严格判别联合,不能用摘要、自由 Summary 或重新查询的当前值替代。billingEvaluation.state=resolved 只允许对应 Snapshot Resolution 已决;Pricing Input Digest 内容寻址该 Dimension 的 Snapshot 四元组/Resolution、Pricing Snapshot、Billing Policy/Rule、Charge/经济身份与 Calculator Version,Target/Settled/Delta 使用同一精度规范 Decimal。unresolved 禁止 Pricing/Amount 字段,只允许Contracts Reason和排序去重的Owner Conflict Fact完整四元组;ownerConflictFactCount == ownerConflictFacts.length,缺输入类Reason可为空集,已声称冲突则至少一项。该集合是Owner证据而非Operations Case列表;本次Billing Finalization Decision如需Case,只按自身run级Identity Reservation新建。成功终局禁止任何 Unresolved;Blocked/Reconciliation Manifest 可以包含它,但不能取得或伪造部分 Validation Bundle。Totals 必须由完整 resolved Dimension 集按同一经济身份精确计算,满足 pendingDelta = targetNet - alreadySettledNet 与 Reservation/Release 规则;不同经济身份不可相加。

Bundle 的 Execution Receipt 必须恰好一份;Metering 数组按 (runStepId, executionAttemptId, chargeDimensionKey, settlementInputSnapshotRef) 排序并与 Manifest 全部 Dimension 一一对应;Gateway 数组按 (runStepId, executionAttemptId, gatewayDeploymentId, providerEvidenceKind, evidenceDimensionKey, availabilitySnapshotRef) 排序并与 Manifest 中实际存在的唯一 Attempt-bound Availability Snapshot 去重集合精确等值。同一Attempt的共享Gateway Receipt只出现一次,但其Run Step仍必须和Manifest/Attempt Fact全等。execution_not_dispatched Dimension 没有 Availability,既不要求也禁止 Gateway Receipt。每个 Receipt 的 Purpose/Operation/Input Manifest 四元组和其 validated* 身份必须与 Bundle Entry、Manifest 和 Owner Read Contract逐项相等;Count、重复、缺项、夹带、错Step、错 Purpose、错经济身份或同 Ref/Schema 异 Digest全部拒绝。

Manifest 与 Bundle 的摘要分别覆盖除 Repository-owned Ref/Time和摘要自身外的完整严格 Candidate,包括所有嵌套 Schema/Digest Algorithm Version、联合分支、Count 和规范数组顺序。Manifest 不包含随后产生的任何 Validation Receipt;Receipt 不引用 Bundle;Bundle 包含已完成 Receipt 后再计算自己的摘要,因此固定构造链保持 Manifest → Receipts → Bundle → Decision 单向无环。Contracts 必须为 Manifest、两个 Resolution/Billing Evaluation 分支、三组 Receipt、空/多 Attempt、多 Dimension、Not-dispatched 去重集合发布 Golden JSON/Canonical Bytes/Digest,以及未知字段/算法、排序、Count、集合等值和摘要篡改负测。

finalizationInput.state=closed_attempt_set 中内容寻址的 FinalizationInputManifest 必须先保存完整 RunExecutionFinalizationFactRunAttemptSetManifest 的 Ref/Schema Version/Digest Algorithm Version/Digest/State Version,并逐项复制其精确排序的 Attempt Manifest 四元组;计费条目中的 Attempt 集必须与该关闭集合严格相等。Manifest 再固定每个 Attempt 的 Contracts 注册 Charge Dimension 集合;条目按 Attempt/Charge Dimension Key/Charge Type/Registry Version/Unit 排序。每个维度必须保存 Current SettlementInputSnapshot@1 的 Ref/Schema Version/Digest Algorithm Version/Digest/State Version及其严格 Resolution 投影,不能从互相独立的 Availability、Eligibility、MeterEvent 查询结果拼接。Precondition 分支没有、也禁止携带这份 Manifest。

只有 gateway_provider_available | gateway_provider_terminal_none Resolution 才保存 Current GatewayEvidenceAvailabilitySnapshot@1 的 Ref/Schema Version/Digest Algorithm Version/Digest/State Version/State,但不包含随后才产生的 Billing Finalization Validation Receipt;Available 另外保存唯一 Active Evidence Head完整四元组,Not-reported/Unavailable严格禁止任何 Evidence四元组。两类 Gateway Resolution均保存 AttemptRouteBinding与 GatewayRouteSnapshot完整四元组。execution_not_dispatched 只保存 AttemptNotDispatchedFact + ExecutionEligibilityFact 四元组与 no-receipt marker,禁止 Availability/Binding/Route/Evidence/Gateway Receipt。Resolution 内的 eligibilityHeadmeterEventHead 必须满足 SettlementInputSnapshot 的同源配对不变量,并另外固定 ExecutionEligibilityFact 四元组、evidenceFinalizationRuleVersion、该维度目标应结净额、缺失/冲突标记、未决 Reconciliation 引用和各 Source Digest。每个已决 Eligibility 的 Rule Version 与 Decision、顶层 Billing Policy/Finalization Decision 必须相等;resolution.state=unresolved 只能产生 blocked/reconciliation Decision。

Billing 先固定 FinalizationInputManifest 摘要,再用这份已知摘要执行三组验证:向 Execution 取得恰好一份关闭事实 Receipt;对每个 Charge Dimension 调用 Metering purpose=billing_finalization 验证其 SettlementInputSnapshot Current Pointer;仅对 Manifest 中两个 Gateway Usage Basis 引用的每个唯一 Gateway Availability Snapshot调用一次 Gateway Validation。Not-dispatched 维度严格不调用 Gateway Validation。Gateway Receipt绑定Finalization Operation、Input Manifest四元组、Attempt、Gateway Deployment、Provider Evidence Kind、固定Evidence Dimension与被验证Availability Snapshot,不携带Gateway无法独立验证的Run Step或Billing chargeDimensionKey;Run Step只由Bundle Entry作为Billing侧坐标保存,并必须与Manifest、Attempt Manifest和Metering Entry全等。同一个 attempt_aggregate@1 Usage Snapshot 可以被多个 Billing Charge Dimension 引用,但只产生一份 Gateway Receipt;Input Manifest 摘要已经固定这些维度及其映射。Billing 把单个 executionFinalizationReceipt、按 Run Step/Attempt/Charge Dimension/Snapshot Ref 排序的 meteringSettlementInputReceipts[]、按 Run Step/Attempt/Gateway Deployment/Provider Evidence Kind/Availability Ref 排序的 gatewayAvailabilityReceipts[] 写入独立内容寻址的 FinalizationValidationBundle@1。Bundle 固定 Input Manifest 四元组、三组 Receipt 的 Ref/Schema Version/Digest Algorithm Version/Digest及被验证身份,并计算独立摘要;所有 Receipt 都不反向引用 Bundle。这样 Input Manifest → Receipts → Validation Bundle → Decision 是单向无环链。

settle_and_release_remainder | release_all 要求 finalizationInput.state=closed_attempt_set、每个期望计费维度的 SettlementInput Resolution 为 metered | terminal_none、Eligibility 已决,并要求 finalizationValidation.state=available。Bundle 必须有一份且仅一份 Execution Receipt,并与 Input Manifest 的 Run/关闭事实/Attempt Set 全等;Metering Receipt 集必须与全部 Charge Dimension/Snapshot 精确等值;Gateway Receipt 集必须与 Input Manifest 中实际存在 Availability Snapshot的去重后 (Attempt, Gateway Deployment, Provider Evidence Kind, attempt_aggregate@1, Availability Snapshot) 集合精确等值,Not-dispatched 维度既不计入也禁止夹带 Receipt。终态无 Evidence 由 gateway_provider_terminal_noneexecution_not_dispatched Ineligible Decision明确证明零目标额,绝不能以缺失 Eligibility 或读取当前投影代替。关闭集合下的未决输入只允许产生 blocked | reconciliation_required,并必须用注册 Reason 指明尚缺输入或冲突;这两个 Decision 分支允许 finalizationValidation.state=none,但不得把部分 Receipt 冒充完整 Bundle。尚未形成关闭集合时则只走严格 Precondition 分支,不能伪造空 Input Manifest。成功分支在客户 Ledger 事务前逐项验证 Execution/Metering/Gateway Receipt 与关闭事实、Snapshot/Binding/Route/Evidence(若有)完全相等、Purpose/Input Manifest Digest 正确且覆盖集合完整。Execution Receipt 固定不可逆 Attempt 边界;Metering/Gateway Receipt 固定各自输入线性化边界,边界之后到达的新 Eligibility、MeterEvent 或 Evidence Head 作为 post-boundary correction 处理,不追溯使 Receipt 失效。验证失败时不执行 Billing 终局。Input Manifest 另汇总全维度目标净额、已入账净额、待追加 Settlement/Adjustment/Refund 与应释放余量,所有 Decimal 保持精确。Input Manifest 与 Validation Bundle 的 Digest Algorithm Version、Schema Version 与 Digest 都必须成组保存,算法版本进入所有比较和内容地址;Contracts 分别发布 Golden JSON/Canonical Bytes/Digest。runExecutionFinalization.state=available 必须完整引用 Execution 关闭事实;缺失/冲突时只能使用 Precondition Evaluation 产生 blocked/reconciliation,不能进入任何 Ledger 事务。

此外成功分支必须在同一Billing事务锁定BillingFinalizationFence,要求其executionClosureWatermark.state=closed、Closure Event Consumer Receipt已提交,并把Event ID/Type/Schema/Envelope摘要、Finalization Fact/Attempt Set四元组/State Version/Attempt Count与Trigger、Owner Read、Input Manifest、Execution Receipt逐项比较。任一缺失或不等都只能阻止提交或进入Reconciliation,不能以“Owner现在返回closed”替代尚未应用的Mandatory Event。

inputDigest 使用 Contracts Golden Canonicalizer覆盖严格 Decision Input Candidate的 Reservation/Run、完整 Execution Finalization联合、Policy/Rule、完整 finalizationInput判别联合、Validation、Status/Reason、严格reconciliationCase联合与 Supersedes完整四元组,但明确排除 billingFinalizationDecisionIdempotencyKey、Input Digest Algorithm/Digest自身、最终 Decision Digest Algorithm/Digest以及 Repository-owned Ref/Time。Idempotency Key只由 billingFinalizationDecisionSchemaVersion + inputDigestAlgorithmVersion + inputDigest的规范摘要生成;随后 billingFinalizationDecisionDigest覆盖除 Repository-owned Ref/Time和最终摘要自身外的完整持久 Fact Candidate,包括 Input Digest和 Idempotency Key。构造顺序固定为 Case Identity Reservation → Input Candidate → Input Digest → Key/Decision → Case Request Event → Work Finish,不存在Event或Finish反向进入Decision的摘要环;任一业务字段变化必须先改变 Input Digest。

Repository重算 Input Digest、Idempotency Key与 Decision Digest;首次 insert生成 Ref/Time,同 Key只比较完整严格 Candidate并返回原记录,同键异 Candidate冲突。Supersedes完整四元组必须全有或全无,并指向同一 Reservation/Run/Policy的当前直接前驱;Repository拒绝分叉、循环、跳跃、错版本/摘要和乱序。只有 Reservation仍为 active才可追加新的 Finalization Candidate:blocked可以由后续新输入触发的新Work直接Supersede;reconciliation_required期间的新输入只能推进Watermark并形成held_for_resolution Receipt,必须由当前代Resolution Applied Event开启后继Work后才能Supersede原Decision。settle_and_release_remainder | release_all是吸收态:一旦与 Reservation终局同事务提交,任何后继 Finalization Decision都必须拒绝,同一 Key重放只返回原成功事实。终局后的 Eligibility/MeterEvent/Evidence变化只能进入 BillingSettlementTransitionLateSettlementExposure与对应 Operations Case流程,不能把成功 Head改回 blocked/reconciliation。

blocked | reconciliation_required 禁止写客户 Ledger或修改 Reservation 终态,但它们可以发生在 Execution 已独立关闭之后。settle_and_release_remainder | release_all Decision、把 Manifest 所固定的 Settlement Input 调整到目标净额的 Ledger Transition、Reservation 终局状态、每维度 BillingSettlementInputConsumption@1/Dimension State、Catch-up Fence/Durable Work 与对应余量 Release,只在 Billing 自己的 Repository 中锁定 Billing Account/Reservation/Dimension State,并和 BillingFinalizationCommitted@1、Audit、billing.finalization.committed@1 低敏 Outbox Event及其冻结 Delivery Set原子提交;事务末还必须证明Manifest维度与Fence/Work精确一一对应。任何子分录、Consumption、Fence/Work、Committed Fact、Audit、Event或 Outbox失败全部回滚,Execution关闭事实保持不变,原 Operation/Input/Receipts可确定重试。Event必须携带 Committed Fact完整四元组及其所绑定的 Finalization Decision/Input Manifest/Validation Bundle完整四元组、Reservation/Account/Run、原子提交后的 Reservation与聚合 Revision、Dimension Consumption集合、Correlation/Operation/Causation;Operations只以版本化严格 Payload消费,不得从 Ledger猜测终局。目标净额超过可结算预占,或在任一 Receipt线性化边界之前已存在却未被 Manifest捕获的 Attempt/Settlement Input/Evidence不一致,只能追加 blocked/reconciliation评估;各边界之后的新 Eligibility/MeterEvent/Evidence由Mandatory Settlement Input Event、Billing Watermark和Finalization自建Catch-up Work保证进入终局后Transition/Late Settlement流程,而不是等待下一次偶然通知。新 Attempt与关闭 CAS在 Execution Repository中只能有一个合法顺序,Billing不伪装跨库事务,也不在提交时回查所谓“Current”外部 Row。

成功终局的 Owner证明不是“Decision允许”,而是下列与 Ledger、Release、Reservation和全部 Dimension State同事务提交的内容寻址 Fact:

BillingFinalizationCommitted@1 = {
  billingFinalizationCommittedRef / billingFinalizationCommittedSchemaVersion
  billingFinalizationCommittedDigestAlgorithmVersion = jcs-sha256-v1
  billingFinalizationCommittedDigest
  billingFinalizationOperationId
  billingFinalizationDecisionRef / billingFinalizationDecisionSchemaVersion
  billingFinalizationDecisionDigestAlgorithmVersion / billingFinalizationDecisionDigest
  billingFinalizationDecisionInputDigestAlgorithmVersion / billingFinalizationDecisionInputDigest
  finalizationInputManifestRef / finalizationInputManifestSchemaVersion
  finalizationInputManifestDigestAlgorithmVersion / finalizationInputManifestDigest
  finalizationValidationBundleRef / finalizationValidationBundleSchemaVersion
  finalizationValidationBundleDigestAlgorithmVersion / finalizationValidationBundleDigest
  billingReservationId / billingAccountId / runId
  billingFinalizationFenceId / billingFinalizationFenceRevision
  closureSourceEventId / closureSourceEventType / closureSourceEventSchemaVersion
  closureSourceEventEnvelopeDigestAlgorithmVersion / closureSourceEventEnvelopeDigest
  closureConsumerReceiptRef / closureConsumerReceiptSchemaVersion
  closureConsumerReceiptDigestAlgorithmVersion / closureConsumerReceiptDigest
  billingPolicyRevisionId / evidenceFinalizationRuleVersion
  settlementEconomicIdentity = SettlementEconomicIdentity@1
  status = settle_and_release_remainder | release_all
  dimensionCommitmentCount
  dimensionCommitments[] = sorted {
    runStepId / executionAttemptId / chargeDimensionKey
    settlementInputConsumptionRef / settlementInputConsumptionSchemaVersion
    settlementInputConsumptionDigestAlgorithmVersion / settlementInputConsumptionDigest
    resultingDimensionStateRevision
  }
  ledgerEffectCount
  ledgerEffects[] = sorted {
    runStepId / executionAttemptId / chargeDimensionKey
    ledgerEffectRef / ledgerEffectSchemaVersion
    ledgerEffectDigestAlgorithmVersion / ledgerEffectDigest
  }
  releaseEffect =
    { state=none }
    | { state=applied; releaseEffectRef / releaseEffectSchemaVersion;
        releaseEffectDigestAlgorithmVersion / releaseEffectDigest }
  resultingReservationStatus = settled | released
  resultingReservationRevision
  committedAt
}

dimensionCommitments[]ledgerEffects[] 都按 (runStepId, executionAttemptId, chargeDimensionKey) 排序并拒绝重复;每个 Entry 的 Step/Attempt/Dimension 必须与 Input Manifest、Consumption、Dimension State及实际 Ledger逐项相等。Committed Event必须原样携带两组 Count和严格集合,Operations只从Event构造Owner Read并比较返回Fact,不能从Attempt反查或猜测Step。Contracts负向测试覆盖缺失/错runStepId、同Attempt跨Step重绑、集合排序/Count不等,以及Owner Fact与Event任一坐标不一致。

Committed Digest覆盖除 Repository-owned Ref/Time和摘要自身外的完整严格 Candidate;两个集合按 Contracts注册键排序、拒绝重复并与 Count全等。Decision、Manifest、Bundle、Closure Watermark/Event Consumer Receipt、每维度 Consumption/State、Ledger/Release Effect、终态 Reservation、经济身份与实际同事务记录必须双向精确等值;Fence Revision必须仍等于当前Work/Trigger且同事务推进到committedrelease_all只允许零目标、无 Ledger Effect与 released终态;settle_and_release_remainder只允许 Ledger精确实现目标净额与 settled终态。Repository对 (billingReservationId, billingFinalizationOperationId)及成功 Decision完整四元组唯一并 insert-or-compare;重放返回首次 Fact,同键异 Candidate冲突。blocked | reconciliation_required不得产生该 Fact或 Committed Event。

Billing 经济事实定向 Read Contract

Operations Projector与受限 Admin Audit不直连 Billing数据库,也不从 Event、Ledger或当前状态拼正文。Billing提供三类版本化读取:

readBillingFinalizationCommitted(
  billingFinalizationCommitted完整四元组,
  expected billingFinalizationOperationId, billingReservationId, billingAccountId, runId
)
  -> { result=found; fact=BillingFinalizationCommitted@1 }
   | { result=not_found }
   | { result=conflicting; conflictRef }

readBillingSettlementTransition(
  billingSettlementTransition完整四元组,
  expected billingTransitionOperationId, billingReservationId, billingAccountId, runId,
  runStepId, executionAttemptId, chargeDimensionKey, fromStateRevision, toStateRevision
)
  -> { result=found; fact=BillingSettlementTransition@1 }
   | { result=not_found }
   | { result=conflicting; conflictRef }

readLateSettlementExposure(
  lateSettlementExposure完整四元组, expected exposureRevision,
  billingReservationId, billingAccountId, runId, runStepId,
  executionAttemptId, chargeDimensionKey, expectedState
)
  -> { result=found; fact=LateSettlementExposure@1 }
   | { result=not_found }
   | { result=conflicting; conflictRef }

三个入口只接受精确 Operations Projector或受限 Admin Audit Workload Audience/Scope;服务端必须按完整四元组读取不可变版本,严格解码并重算摘要,再比较所有期望身份、Revision、SettlementEconomicIdentity@1与 Repository绑定。调用方也必须重算并把正文与同一 Event自包含字段逐项比较。readBillingFinalizationCommitted还验证 Decision/Input Digest、Manifest、Bundle、集合 Count和提交结果;readBillingSettlementTransition验证 Account/Run/Step、Consumption、Pointer、Ledger与 Case Action;readLateSettlementExposure验证直接前驱、Open/Resolved联合与 Current Source身份。三者都不提供裸 Ref、latest、列表枚举或宽松投影回填;进程重启与 Shadow Rebuild必须只凭保留 Event中的完整四元组和期望身份恢复。Contracts发布请求/响应严格 DTO、Golden JSON/Canonical Bytes/Digest,并覆盖 wrong Audience/Scope、未知 Schema/算法、同 Ref/Schema异 Digest、跨 Account/Run/Step、期望身份/Revision/经济身份错配、正文篡改和响应丢失重放。

首期 lateEligibleDisposition 固定为 reconcile_before_charge:Reservation 已释放后的任何正差额,Metering 仍保留规范事实,但 Billing 不自动创建 Base、扣成负余额或绕过预算,而是以原 Reservation、Current MeterEvent Head 和经济暴露幂等创建 Reconciliation Case。只有经 Admin Command Gateway 授权后,Billing Domain 才能执行不可变 LateSettlementCommand@1,追加明确的 Post-release Adjustment。平台承担、供应损失或核销尚未定义可执行命令;在未来以独立 Billing-owned Disposition Contract、Ledger Effect与Applied Fact正式发布前,Admin只能提出方案,不能本地关闭Case或把“未扣款”伪装为已结算。原 Reservation 始终保持 released。余额不足、授信失效、Head 变化或审批缺失均保持 Case 待处置。未来如需自动 Post-release 扣款必须发布新的 Billing Policy Revision 与独立 ADR,不能回溯改变历史 Run。

正差额执行前必须由 Billing 自己的 Account/Funding 模块签发一次性 FundingDecision@1;首期 Billing Account、钱包余额桶和信用额度记录必须位于同一 Billing Repository/事务 Owner:

fundingDecisionRef / fundingDecisionSchemaVersion
fundingDecisionDigestAlgorithmVersion = jcs-sha256-v1
fundingDecisionDigest
fundingDecisionInputDigestAlgorithmVersion = jcs-sha256-v1
fundingDecisionInputDigest
fundingDecisionIdempotencyKey
fundingDecisionJti
status = authorized
billingAccountId / expectedBillingAccountRevision
billingReservationId / runId / runStepId / executionAttemptId / chargeDimensionKey
reconciliationCaseRef / caseGeneration
reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion
reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest
lateSettlementExposureRef / lateSettlementExposureSchemaVersion
lateSettlementExposureDigestAlgorithmVersion / lateSettlementExposureDigest / exposureRevision
lateSettlementOperationId
fundingPolicyRevisionId
fundingSource =
  { kind=wallet_balance; balanceBucketId; expectedBalanceRevision }
  | { kind=entitlement_bucket; entitlementBucketId; expectedEntitlementRevision;
      entitlementKey; unit }
  | { kind=credit_line; creditLineId; expectedCreditLineRevision }
authorizedAmountDecimal
settlementEconomicIdentity = SettlementEconomicIdentity@1
notBefore / expiresAt
decidedAt

fundingDecisionInputDigest覆盖调用方稳定提供、且已由 Billing验证的严格业务输入:Account/Expected Revision、Reservation/Run/Step/Attempt/Dimension、Case Ref/Generation/Identity Reservation完整四元组、Current Exposure完整四元组/Revision、Operation、Funding Policy Revision、Funding Source联合、授权金额与经济身份;明确排除 Repository生成的 Decision Ref/Time、fundingDecisionJtinotBefore/expiresAt、Idempotency Key、最终 Decision Digest及 Input Digest自身。Idempotency Key只由 fundingDecisionSchemaVersion + fundingDecisionInputDigestAlgorithmVersion + fundingDecisionInputDigest生成。Billing必须先按唯一业务键 (lateSettlementOperationId, Current Exposure完整四元组/Revision)读取:已有记录且业务输入/Input Digest相等则返回首次 Decision;异输入冲突。只有 not_found时才在锁定事务生成终身唯一 JTI、按冻结 Policy和首次 decidedAt生成 notBefore/expiresAt,再计算覆盖全部持久字段(排除 Ref/Time和摘要自身)的最终 Decision Digest并插入;并发唯一键失败必须重读、比较业务输入并返回赢家,不能保留本 Worker预生成的 JTI/时间窗或报告伪冲突。调用方不得提交 JTI、有效窗、Decision Ref/Time、Key或最终 Digest。

authorizedAmountDecimal必须精确等于 Current Exposure的正差额,Account、Reservation、Run、Step、Attempt、Dimension、Case Ref/Generation/Identity Reservation、Exposure四元组/Revision、Operation、Funding Policy Revision与 SettlementEconomicIdentity@1必须逐项相等。Funding Source是严格三分支联合:Entitlement身份只能使用同 Key/Unit的 Entitlement Bucket;Credits只能使用 Credits Balance Bucket;Money只能使用同 Currency的 Money Balance或 Credit Line,具体允许组合、有效期和上限由不可变 fundingPolicyRevisionId制品摘要固定。分支混合、把 Currency当 Unit、跨 Entitlement Bucket或省略 Policy Revision都拒绝。billingAccountRevision是 Billing Repository的单调 CAS版本,任何 Reservation、Settlement、Adjustment/Refund、Funding Decision Consumption、余额桶、权益桶或信用额度可用额变化都必须在同一事务推进;不得只在资料编辑时更新。历史重放返回首次冻结 Policy/JTI/有效窗,不能套用当前策略;已过期且未消费时必须使用新的 Late Settlement Operation重新走同一代 Case的 Approval/Funding流程,不能原地延长;若该 Case已进入 resolving,任何新正 Exposure必须先创建下一 Generation,旧代 Funding Decision不得复用。执行时在同一 Ledger事务锁定 Account与所选 Funding Source Revision,并以 JTI唯一约束原子消费;金额不足、Revision漂移、超时、跨 Run/Step/Case/Generation/Identity Reservation/Exposure/Operation/Policy/经济身份重放或第二次消费全部拒绝。Contracts测试覆盖响应丢失、双 Worker各自产生不同临时 JTI/时钟、唯一键竞争、同业务键异 Run/Step/Case Generation/Reservation/金额/来源/Policy/经济身份,并证明只保存和返回一个首次 Decision。未来若把 Credit拆到外部 Owner,必须先增加绑定完整 Decision Candidate的 Current Validation Receipt;首期不得用裸 creditAuthorizationRef伪装跨库一致性。

LateSettlementCommand@1 是严格、封闭的 Billing Domain Payload,并作为 AdminCommandCandidate@1.commandRequest.payload 的注册分支:

lateSettlementCommandSchemaVersion = 1
lateSettlementCommandIdempotencyKey
reconciliationCaseRef / expectedCaseRevision / expectedCaseGeneration
reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion
reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest
billingReservationId / billingAccountId / expectedBillingAccountRevision
priorFinalizationDecisionRef / priorFinalizationDecisionSchemaVersion
lateSettlementOperationId
runId / runStepId / executionAttemptId
lateSettlementExposureRef / lateSettlementExposureSchemaVersion
lateSettlementExposureDigestAlgorithmVersion / lateSettlementExposureDigest / expectedExposureRevision
expectedSettlementStateRevision
expectedLastEvaluatedSettlementInputSnapshotRef / expectedLastEvaluatedSettlementInputSnapshotSchemaVersion
expectedLastEvaluatedSettlementInputSnapshotDigestAlgorithmVersion / expectedLastEvaluatedSettlementInputSnapshotDigest
expectedLastEvaluatedSettlementInputStateVersion
expectedLastEvaluatedEligibilityDecisionRef / expectedLastEvaluatedEligibilityDecisionSchemaVersion
expectedLastEvaluatedMeterEventHead = { state=none } | { state=available; meterEventRef; meterEventSchemaVersion }
expectedSettledSettlementInputSnapshotRef / expectedSettledSettlementInputSnapshotSchemaVersion
expectedSettledSettlementInputSnapshotDigestAlgorithmVersion / expectedSettledSettlementInputSnapshotDigest
expectedSettledSettlementInputStateVersion
expectedSettledEligibilityDecisionRef / expectedSettledEligibilityDecisionSchemaVersion
expectedSettledMeterEventHead = { state=none } | { state=available; meterEventRef; meterEventSchemaVersion }
currentMeterEventRef / currentMeterEventSchemaVersion
chargeDimensionKey / pricingSnapshotId / billingPolicyRevisionId
previousSettledAmountDecimal / currentTargetAmountDecimal / positiveDeltaDecimal
settlementEconomicIdentity = SettlementEconomicIdentity@1
fundingDecisionRef / fundingDecisionSchemaVersion
fundingDecisionDigestAlgorithmVersion / fundingDecisionDigest / fundingDecisionJti
fundingPolicyRevisionId
approvalDecisionRef / approvalDecisionSchemaVersion / expectedApprovalRevision
approvalSetDigestAlgorithmVersion = jcs-sha256-v1
approvalSetDigest
jitAuthorization =
  { kind=not_required; jitPolicyRevisionId }
  | { kind=required; jitGrantRef / jitGrantSchemaVersion; expectedJitGrantRevision;
      jitScopeDigestAlgorithmVersion / jitScopeDigest }
actorWorkforcePrincipalId
lateSettlementCommandBasisDigestAlgorithmVersion = jcs-sha256-v1
lateSettlementCommandBasisDigest

成功执行只产生一份不可变、可内容验证的 LateSettlementCommandResult@1;它是 Billing 对响应丢失重放和 Operations 读取解决来源的正式结果,而不是 HTTP 成功提示或自由格式 Execution Evidence:

LateSettlementCommandResult@1 = {
  lateSettlementCommandResultRef / lateSettlementCommandResultSchemaVersion
  lateSettlementCommandResultDigestAlgorithmVersion = jcs-sha256-v1
  lateSettlementCommandResultDigest
  lateSettlementCommandResultIdempotencyKey
  status = applied
  lateSettlementOperationId
  lateSettlementCommandSchemaVersion / lateSettlementCommandIdempotencyKey
  lateSettlementCommandBasisDigestAlgorithmVersion / lateSettlementCommandBasisDigest
  adminAuthorization = {
    adminCommandCandidateRef / adminCommandCandidateSchemaVersion;
    adminCommandCandidateDigestAlgorithmVersion / adminCommandCandidateDigest;
    signedWorkforceCommandExecutionGrantRef / signedWorkforceCommandExecutionGrantSchemaVersion;
    signedWorkforceCommandExecutionGrantDigestAlgorithmVersion / signedWorkforceCommandExecutionGrantDigest;
    consumedCommandGrantJti;
    actorWorkforcePrincipalId
  }
  reconciliationCaseRef / caseGeneration
  reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion
  reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest
  billingReservationId / billingAccountId
  runId / runStepId / executionAttemptId / chargeDimensionKey
  pricingSnapshotId / billingPolicyRevisionId / fundingPolicyRevisionId
  settlementEconomicIdentity = SettlementEconomicIdentity@1
  authorizedOpenExposureRef / authorizedOpenExposureSchemaVersion
  authorizedOpenExposureDigestAlgorithmVersion / authorizedOpenExposureDigest / authorizedExposureRevision
  resolvedExposureRef / resolvedExposureSchemaVersion
  resolvedExposureDigestAlgorithmVersion / resolvedExposureDigest / resolvedExposureRevision
  funding = {
    fundingDecisionRef / fundingDecisionSchemaVersion;
    fundingDecisionDigestAlgorithmVersion / fundingDecisionDigest;
    consumedFundingDecisionJti
  }
  meteringValidation = {
    validationReceiptRef / validationReceiptSchemaVersion;
    validationReceiptDigestAlgorithmVersion / validationReceiptDigest;
    validationPurpose = { kind=late_settlement_command; lateSettlementOperationId;
      lateSettlementCommandBasisDigestAlgorithmVersion / lateSettlementCommandBasisDigest };
    validatedSettlementInputSnapshotRef / validatedSettlementInputSnapshotSchemaVersion;
    validatedSettlementInputSnapshotDigestAlgorithmVersion / validatedSettlementInputSnapshotDigest;
    validatedSettlementInputStateVersion;
    validatedSettlementEconomicIdentity=SettlementEconomicIdentity@1
  }
  appliedAmountDecimal
  ledgerEffect = {
    state=applied;
    ledgerEffectRef / ledgerEffectSchemaVersion;
    ledgerEffectDigestAlgorithmVersion / ledgerEffectDigest;
    settlementEconomicIdentity=SettlementEconomicIdentity@1;
    amountDecimal
  }
  resultingSettlementState = {
    fromStateRevision / toStateRevision;
    lastEvaluatedInput=BillingSettlementInputPointer@1;
    settledInput=BillingSettlementInputPointer@1
  }
  recordedAt
}

Result Idempotency Key 只由 lateSettlementCommandResultSchemaVersion + lateSettlementCommandSchemaVersion + lateSettlementCommandIdempotencyKey 的 Contracts Canonical 摘要生成,不依赖随后才产生的 Result Ref、Ledger Ref、Resolved Exposure 或 Applied Fact。Result Digest 覆盖除 Repository-owned Result Ref/Time和摘要自身外的完整严格 Candidate,包括 Idempotency Key、授权/命令 Basis、两个已消费 JTI、Metering Receipt、Open→Resolved Exposure、Ledger Effect、经济身份与结算后 State;BillingCaseResolutionApplied@1 只在 Result 完成后单向引用它,Result 禁止反向引用 Applied Fact,因此不存在摘要环。

Billing 在同一 Ledger 事务以 Command Result Key、Grant JTI、Funding Decision JTI 和 (lateSettlementOperationId, authorized Open Exposure Revision) 唯一约束 insert-or-compare;Result 的 Case Ref/Generation/Identity Reservation、appliedAmountDecimal、Ledger Amount、Funding Authorized Amount、Open Exposure Positive Delta、经济身份和 Policy Revision 必须逐项相等,Resolved Exposure 必须以该 Open Exposure 为直接前驱,两个 Result Pointer 必须等于事务提交后的真实 Dimension State。完全相同的 Command/JTI/Candidate 重放返回首次 Result;任一 Key/JTI 绑定不同 Candidate、Case Generation/Reservation、Exposure、金额、Receipt、Policy、经济身份或 State 都在零新业务副作用下拒绝。失败或拒绝命令不伪造 status=applied Result,而是使用既有 command.rejected | command.failed Audit;若事务已提交而响应丢失,重放必须从唯一 Result 返回原成功响应。

Billing 提供两种定向读取。Operations Case Consumer 与受限 Admin Audit 使用 readLateSettlementCommandResult,提交 Result四元组以及期望 Operation、Account/Reservation/Run/Step/Attempt/Dimension、Case Ref/Generation/Identity Reservation、完整 Resolved Exposure四元组/Revision;结果严格为 {result=found; result=LateSettlementCommandResult@1} | {result=not_found} | {result=conflicting; conflictRef}。命令执行入口另提供 readLateSettlementCommandResultByKey:只接受当前已认证 Billing Command Workload,输入严格 Command Candidate、Admin Candidate/Grant身份与由 Command Schema/Key推导的 Result Key,返回相同三分支,但不要求调用方预先知道 Result Ref。两种读取的服务端与读方都必须重算摘要并校验 Repository不可变绑定、Authorization、Command Basis、Case Generation/Reservation、Exposure、Funding、Receipt、Ledger、State和经济身份;Outbox中的裸 Ref或投影摘要不能替代该读取契约。Contracts必须发布 Result、Idempotency Candidate、两种 Read DTO的 Golden JSON/Canonical Bytes/Digest,以及响应丢失重放、两个 JTI并发消费、同 Key异 Candidate、跨 Run/Step/Case Generation/Exposure/Policy/经济身份、Receipt/Purpose/State/Ledger篡改、未知字段/Schema/算法和 Result↔Applied Fact单向关系负测。

positiveDeltaDecimal 必须是严格正的规范十进制,且精确等于 currentTargetAmountDecimal - previousSettledAmountDecimal;零或负差额不能使用该命令。命令、Last Evaluated/Settled Pointer、Current Exposure、Funding Decision、Funding Policy、Command Result、Ledger Effect 与后续 Applied Fact 的 SettlementEconomicIdentity@1 必须完整逐项相等;跨 Credits/Entitlement/Money、错 Entitlement Key/Unit、错 Currency 或 Policy Revision 缺失全部拒绝。lateSettlementCommandBasisDigest 覆盖严格 Payload 中除 lateSettlementCommandIdempotencyKey、Basis Digest Algorithm/Digest、尚未生成的 Metering Validation Receipt,以及 Repository-owned Result Ref/Time之外的全部字段。Domain Idempotency Key 再固定由 lateSettlementCommandSchemaVersion + lateSettlementCommandBasisDigestAlgorithmVersion + lateSettlementCommandBasisDigest 的 Contracts Canonical 摘要生成,不重复反向展开会包含 Key 的 Payload。Trace、Validation Receipt 和外层 Admin Candidate Ref/Digest 均不进入 Basis 或 Key。这样链路固定为 Payload-without-Key/Digest/Receipt → Basis Digest → Domain Key → Receipt/Result,Payload、Basis 与 Key 都不反向引用包裹它的 AdminCommandCandidate@1 或后续 Receipt,不存在摘要自引用环。调用者不能提交 Repository-owned Result ID/Time,也不能覆盖计算出的 Digest/Key。

Billing 先验证当前调用方是该 Command Endpoint 的注册 Workload、严格解码 AdminCommandCandidate@1 + Signed Workforce Command ExecutionGrant + LateSettlementCommand@1,重算外层 Request/Candidate/Basis Digest和 Result Key;随后必须在任何 Grant/Funding有效期、当前 State或 JTI未消费检查之前,以该 Key调用 readLateSettlementCommandResultByKey。若已有 Result且完整 Command/Admin Candidate/Grant/Case Generation/Reservation/Funding候选逐项相等,直接返回首次成功,即使重试时 Grant或 Funding Decision已经到期;已提交事实不能因当前时钟改变为失败。若 Key已存在但候选不同则冲突;只有 not_found 才继续新执行校验。新执行要求 Payload 的 Aggregate Approval Decision Ref/Schema/Revision、Approval Set Digest、JIT联合、Case Ref/Revision/Generation/Identity Reservation、Current Late Settlement Exposure、Funding Decision完整四元组/JTI、fundingPolicyRevisionIdSettlementEconomicIdentity@1 都和 Candidate及 Grant Claims逐项相等。LateSettlementCommand@1 的 Grant Refinement必须在通用完整 Candidate/Request Digest之外显式回显这些 Case/Funding/Exposure/经济身份字段,避免领域受理方需要从不透明声明猜测 Case Generation、Bucket或 Currency。Core签发 Grant的事务已经构成 Approval/JIT/Case的授权线性化边界;Billing不在自己的事务中对这些外部 Owner做无法原子的“当前仍未撤销”查询。随后 Billing以 lateSettlementOperationId + Basis Digest + expectedLastEvaluatedSettlementInputSnapshot 调用 Metering,取得 purpose=late_settlement_command 的 Current Receipt。

仅在 Result不存在时,才开启单一 Billing锁定事务并逐项验证:Grant签名、Audience、Workload Binding、当前时间窗和 Candidate Digest有效,Grant JTI尚未被本 Domain消费;原 Reservation仍 released、Finalization Decision与 MeterEvent Head仍为声明版本、State Revision、lastEvaluatedInputsettledInput仍精确等于命令声明、Receipt的 Purpose/Basis/Snapshot全等、Current Exposure仍是声明的 Open Head且正差额不变、State中的 Case Ref/Generation/Identity Reservation与命令完全一致、Pricing/Billing Policy/Dimension/Charge Unit与严格经济身份一致、actorWorkforcePrincipalId == Candidate.actor == Grant.sub,Funding Decision签名摘要、JTI、Account/Source Revision、Case Generation/Reservation、金额、fundingPolicyRevisionIdSettlementEconomicIdentity@1、Exposure、Operation和当前有效期全部匹配。Billing不锁或重读 Operations-owned Case/Approval/JIT。Ledger Transition Idempotency Key可以单向包含已验证的 Candidate Ref/Digest、Domain Command Key、Grant Ref/JTI、Funding Decision四元组/JTI与 Metering Receipt四元组;Candidate Digest绝不覆盖该 Transition Key。事务以 Result Key、Grant JTI与 Funding Decision JTI三个唯一约束原子追加 Consumption Record、Post-release Ledger Adjustment、Resolved Exposure、把 settledInput CAS推进到 lastEvaluatedInput、追加包含全部凭证四元组和相同经济身份/Policy Revision的 Command Result/Execution Evidence、BillingCaseResolutionApplied@1及对应低敏经济边界 Outbox,并把本地 Case Link推进为同 Generation的 resolving;任一校验、Consumption、Funding Source、Ledger、State、Result、Applied Fact、Outbox或 Audit失败全部回滚且不推进任何 Revision。唯一键竞争失败后必须重新走“先查 Result并完整比较”的分支,不能重新做当前时间窗判断。Operations后续按 Case Generation与 Exposure Source Revision幂等关闭该代 Case;Case本地 CAS/投影失败只重试 Outbox消费,绝不回滚已提交扣款。此后若再次出现正差额,Billing必须预留下一代 Case身份;旧代 Ack、Approval、JIT、Grant或 Funding Decision均不得复活或覆盖新代 Link。Grant签发边界后的 Approval/JIT撤销不追溯取消已经授权的这一次执行;尚未签发的新命令立即受撤销影响,紧急硬停止若需覆盖已签发 Grant必须另行设计由 Billing所有并原子消费的撤销协议,不能退回远程 TOCTOU检查。同 Key同 Candidate返回首次结果,同 Key异 Candidate、Case Generation/Reservation、Snapshot/Receipt/Head/Exposure/Funding Revision漂移、Funding Policy/经济身份错配、余额/权益/授信不足全部拒绝且零业务副作用。测试覆盖摘要无环构造、签发前审批/JIT撤销、签发后撤销非追溯、新执行 Grant/Funding过期、到期前提交后响应丢失并跨到期重放、两类 JTI同 Candidate重放/异 Candidate偷换、跨 Case Generation/金额/Policy/Credits/Entitlement Bucket/Unit/Currency/Exposure Funding重放、release→late eligible、eligible增额、Receipt后新 Snapshot、重复 Head/Command、并发 Account消费、待批期间新 Head、资金/权益充足与不足、Case resolve Outbox重试,以及 I1→E2→I3→E4多次资格翻转时新代先开、旧代 Ack乱序到达。

Finalization 测试还必须覆盖空 Attempt、多个 Attempt/Dimension、以 Terminal-none Eligibility Decision 证明的零费用 release_all、已有一条/多条 Eligible Head 的 settle_and_release_remainder、先部分到达后维度封闭、目标净额等于/小于/超过预占、available 缺 Evidence Ref/Version、非 available 偷带 Evidence、Snapshot/Validation Receipt Purpose或摘要篡改、Receipt 线性化边界后新 Head、Receipt 反向引用 Bundle/Manifest 的循环构造、Metering/Gateway Bundle 缺项/重复/夹带/错 Input Manifest、Pending/Conflicting Availability、成功 Input Manifest 出现未决 Resolution、Eligibility 或 MeterEvent Head 竞争更新、Input Manifest 漏项/乱序/摘要篡改、同 Key 不同到达时间、新 Attempt 与终局并发、Supersession 分叉,以及任一 Settlement/Adjustment/Refund/Release Ledger 提交失败;任何失败都不能留下 Decision 已允许但账务/Reservation 未终局,或 Reservation 已终局却没有对应 Decision/完整 Ledger 的裂缝。

每条规范供应成本事实使用封闭 ProviderCostFact@1 DTO:

ProviderCostFact@1 = {
  providerCostFactRef / providerCostFactSchemaVersion
  providerCostFactDigestAlgorithmVersion = jcs-sha256-v1
  providerCostFactDigest
  idempotencyKey / providerCostValidationOperationId
  providerCostFactCandidateDigestAlgorithmVersion = jcs-sha256-v1
  providerCostFactCandidateDigest
  executionAttemptId / gatewayDeploymentId
  sourceEvidenceDimensionKey / providerCostDimensionKey
  providerCostMappingRevisionId
  routeCostBindingRef / routeCostBindingSchemaVersion
  routeCostBindingDigestAlgorithmVersion / routeCostBindingDigest
  attemptRouteBindingRef / attemptRouteBindingSchemaVersion
  attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest
  gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion
  gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest
  costAvailabilityRef / costAvailabilitySchemaVersion
  costAvailabilityDigestAlgorithmVersion / costAvailabilityDigest / costAvailabilityStateVersion
  providerCostEvidenceRef / providerCostEvidenceSchemaVersion
  providerCostEvidenceDigestAlgorithmVersion / providerCostEvidenceDigest
  costAvailabilityValidationReceiptRef / costAvailabilityValidationReceiptSchemaVersion
  costAvailabilityValidationReceiptDigestAlgorithmVersion / costAvailabilityValidationReceiptDigest
  routeCostValidationReceiptRef / routeCostValidationReceiptSchemaVersion
  routeCostValidationReceiptDigestAlgorithmVersion / routeCostValidationReceiptDigest
  costAmountDecimal
  currency / currencyRegistryVersion
  costUnit / costUnitRegistryVersion
  normalizationPolicyVersion
  supersedesProviderCostFact =
    { state=root }
    | { state=supersedes;
        providerCostFactRef / providerCostFactSchemaVersion;
        providerCostFactDigestAlgorithmVersion / providerCostFactDigest }
  correctionReason?
  occurredAt / recordedAt
  traceId? / correlationId / operationId
}

MeterEvent 路径中,tenantKind + tenantId + workspaceId + projectId? + billingAccountId + billingReservationId + actorPrincipalId + executionPrincipalId + product + runId + modelOfferingRevisionId + pricingSnapshotId + billingPolicyRevisionId + correlationId + operationId 必须与 Run 的不可变 RunAdmissionManifestrun.created@2.0 逐项一致。product 只能继承冻结的 source.surface:Developer Credential 为 developer_api,Playground Grant 为 developer_playground,Customer Session 与 productSurface 相等,Delegated Agent 与 originatingSurface 相等;只有 Delegated Agent 的 MeterEvent 必须且允许携带与 Manifest 相等的 agentRevisionId,其他分支必须省略。Authentication 也严格匹配四分支及其不可变 Authorization Evidence:Developer Credential 与 Playground 都要求 MeterEvent 的 serviceAccountId 等于 Manifest,并由 Principal Registry 的不可变同 Tenant 绑定解析到同一 Execution Principal;Customer Session 与 Delegated Agent 必须省略 Service Account,并分别验证 Assertion 的 Actor=Execution Principal,或 Delegation Grant 的 Actor/同 Run agent_run Principal/Agent Revision 等值。Grant/Assertion/Authorization Decision 的 Ref/ID、Schema Version、Digest Algorithm Version、Digest 必须与 Manifest/Event 相等;Metering必须先按 Manifest/Event冻结的 AuthorizationEvidenceEvaluationSet完整四元组、统一 archiveCutRevision 调用精确 Set Read Contract,再按 Set中冻结的成员四元组读取各 AuthorizationEvidenceEvaluation,验证同一 Operation、Authentication分支期望成员、Archive Cut、服务端评估时间、Actor/Execution Principal、Tenant/Scope与全员 valid。它不得逐成员查询当前状态、混合不同 Cut,或按当前 Archive Revision、Session、授权/撤销 Timeline重算历史。首批 Producer尚未启用后两个分支,未通过 Capability Gate的非 Service Account Run不能进入 Metering。callerWorkloadPrincipalId只能取受信服务跳转主体,不能替代 Actor、Execution Principal或 Service Account归因。executionAttemptId + modelDeploymentId必须与该次 AttemptExecutionManifest一致;其 Eligibility Decision、Usage Evidence、Binding、Route Snapshot与执行锚点也必须属于同一 Attempt/Charge Type。后续重试只有在 Run准入时冻结的 modelRoutingPolicyRevisionId允许时才能改变 Deployment,并为新 Attempt形成新清单、Gateway执行锚点、Binding与 Route Snapshot;不得用新路由覆盖旧清单或旧 Binding。modelOfferingId如需用于聚合只能作为可重建派生维度,不能替代 Revision。上述任一身份、租户、Product/Agent、授权 Evaluation Set/成员、账务、Offering、Pricing/Policy、Eligibility、Attempt、Deployment、Binding、Route或 Usage Evidence错配都进入客户计量 Reconciliation,且不得产出可结算 MeterEvent。Contracts/Repository测试必须覆盖两 Principal缺失/互换、Workload冒充业务 Actor、Service Account资源 ID缺失/错误/跨 Tenant、把 serviceAccountId当 Principal或把 principalId填进 Service Account字段、临时授权版本/摘要/Set不匹配、Set缺项/夹带/错 Authentication分支/不同 Archive Cut、Customer Assertion与 Agent Delegation错配、跨 Run Agent Principal、Product/Surface/Agent Revision错配、未启用分支、跨 Tenant/Billing/Reservation/Run/Operation错绑、跨 Reservation更正和策略更新/撤销后的历史重放。

ProviderCostFact 走独立供应成本路径,不要求也不包含 Offering Revision、Pricing Snapshot 或 modelDeploymentId。它校验 executionAttemptId + gatewayDeploymentId + AttemptRouteBinding完整四元组 + GatewayRouteSnapshot完整四元组 + Cost Availability完整四元组/State Version + Provider Cost Evidence完整四元组,以及 Cost Evidence 内互斥执行锚点与同一 Binding/Route/Availability Active Head 的一致性;correlationId + operationId 必须从该 Attempt 受信关联的 Run/RunAdmissionManifest 解析并逐项相等,不能信任 Provider、Observation 或调用参数自报。自身 Lineage 和两份 Validation Receipt 完整时即可追加 Cost Fact;客户计量链尚未完成不阻塞这条事实。Gateway权威Availability自身为conflicting时才写ProviderCostAvailabilityProcessingFact@1.outcome=reconciliation_required并终结本代;其他schema/identity/mapping/normalization/receipt/repository错配只能写ProviderCostProcessingConflictEvidence@1 + MeteringInputWorkAttemptBlocked@1,保留同Work可见阻塞并在修复后重试,不得写Processing Fact、Finished或terminal CAS。它与客户MeterEvent/Settlement的关联不完整或冲突时,本阶段只形成Metering Owner内可定向读取的跨链异常状态与不完整告警,不把它伪装成Operations完整投影。当前严格Case联合不包含Provider Cost分支,不得伪造reconciliationCaseRef或借Billing Case命令终结该异常;未来确需可靠Admin队列或有状态供应商对账Case时,必须先发布对应Canonical Event/Mandatory Delivery/Owner Read,若引入Case再另发Identity、终态Authority与响应丢失恢复契约。

costAmountDecimal 是非负规范十进制字符串,禁止负号、指数、NaN/Infinity、超出 Registry 精度和非规范前导形式;currency + currencyRegistryVersioncostUnit + costUnitRegistryVersion 都必须是 Contracts Registry 在 Evidence 发生时支持的组合,不能用自由字符串或当前最新版回填历史。Provider 的 signed credit/adjustment 保留在 Evidence;规范成本 Head 仍表达非负累计值,财务有符号差额只由成本对账比较合法 Head 得出。

sourceEvidenceDimensionKey 必须等于 Provider Cost Evidence 的 evidenceDimensionKeyproviderCostDimensionKey 由它通过 Route Snapshot 冻结并由 Contracts 注册的 providerCostMappingRevisionId 映射得到,独立于 Currency/Cost Unit Registry Version。Mapping Revision 固定输入字段、映射表和输出维度,历史重放禁止读取当前 Registry。ProviderCostFact 的 normalizationPolicyVersion 必须等于 Cost Evidence Read Contract 与 Route Snapshot 逐项相等的 providerCostNormalizationPolicyVersion;它和 Mapping Revision 都在 Provider 调用前冻结,不能在 Evidence 到达时选择。

ProviderCostFactCandidate@1 的摘要覆盖完整确定性业务字段、Cost Availability Snapshot 四元组/State Version、Provider Cost Evidence 四元组和 Route Cost Binding 四元组,但明确排除 Fact ID/Recorded Time、Candidate Digest 自身、providerCostValidationOperationId 和随后生成的两份 Validation Receipt。Metering先用已接受Gateway Event绑定的Source Bootstrap读取Availability/Evidence/Route正文,再重算Candidate Digest,并构造两个验证端共同可验证的providerCostValidationScope = executionAttemptId + gatewayDeploymentId + AttemptRouteBinding完整四元组 + GatewayRouteSnapshot完整四元组 + providerEvidenceKind=provider_cost + sourceEvidenceDimensionKey。Contracts固定Namespace按purpose=provider_cost_fact + Candidate带算法摘要 + providerCostValidationScope确定性派生providerCostValidationOperationId;输出providerCostDimensionKey、Mapping与Normalization由Candidate摘要和Route Receipt承诺,不作为Availability端无法验证的公共派生Claim。同一Candidate/Scope跨进程、到达时间和崩溃恢复必须得到同一Operation,任一变化必须得到新Operation。后续Availability Current与Route Binding两项验证请求、JWT和两份Receipt都逐项携带同一Scope、Operation与Candidate摘要;两份Receipt再分别绑定自己的Current Snapshot/Active Evidence或Route Cost Binding/Route Snapshot/Mapping/Normalization,并使用不同Schema与Idempotency Namespace,不能互相替代。Bootstrap响应不能充当Receipt。Metering最后才在单一Repository事务原子保存Candidate Digest、Operation、Scope、两组输入与两份Receipt。不得先写Fact后补Receipt,也不得只保存Route Receipt来推断Evidence仍为Current。

ProviderCostFact 的 idempotencyKey 固定由 providerCostFactSchemaVersion + executionAttemptId + gatewayDeploymentId + sourceEvidenceDimensionKey + providerCostDimensionKey + providerCostMappingRevisionId + Route Cost Binding完整四元组 + Binding完整四元组 + Route完整四元组 + Cost Availability完整五元组 + Cost Evidence完整四元组 + currency/Registry Version + costUnit/Registry Version + normalizationPolicyVersion 的 Contracts 规范摘要确定,不允许实现自行增减字段;providerCostFactRef/recordedAt由Repository首次insert生成,最终Fact Digest覆盖除Repository-owned Ref/Time和摘要自身外的完整严格Candidate,因此任何下游都能使用完整四元组复核;时间、Trace、Correlation/Operation、Metering Operation、Receipt 和 Correction Reason 不进入业务 Key。相同 Key 必须同时比较严格 Candidate、Candidate Digest与最终Fact Digest;首次 insert 还必须证明两份 Receipt 精确绑定同一 Candidate/Operation,并分别绑定 Fact 的 Current Availability/Evidence 与 Route/Mapping。完全相同返回首次 Ref/Time,同 Key 异 Candidate/Digest、任一 Receipt 偷换 Candidate/Input/Operation或四元组缺项都冲突隔离。Repository 对 (executionAttemptId, gatewayDeploymentId, sourceEvidenceDimensionKey) 原子限制唯一 Chain Root,防止 Mapping/Normalization 更新或输出 Dimension 改名为同一来源创建第二 Root;首个 Head 冻结 Source/Output Dimension、Mapping Revision、Binding/Route、Currency/Registry、Cost Unit/Registry 与 Normalization Policy,换版本不得另建第二 Root。更正必须以严格supersedesProviderCostFact.state=supersedes携带当前直接前驱完整四元组,只允许成组推进 Cost Availability/Evidence 四元组、非负累计 Amount、Recorded Time 与规范 Correction Reason;首条必须为state=root。Repository 以唯一 Active Head 与强外键拒绝半联合、第二 Root、分叉、循环、跨 Attempt、跨 Source/Output Dimension/Mapping/Normalization Revision、跨 Binding/Route、跨币种/单位/策略替代和乱序旧 Head。负向测试必须覆盖相同 Evidence 在 Mapping/Normalization 更新后重放、同一 Source Dimension 映射到不同输出、伪造 Source Dimension、不同 Source Evidence 错绑、两份 Receipt/Candidate/Availability/Evidence/Binding/Operation 任一错配,以及 E1 read 后 Current 已推进为 E2/A2 的竞态;所有场景都不得重复归集成本。

MeterEvent、EligibilityDecision 与 ProviderCostFact 的 Create Command 都禁止调用方提交 Repository-owned ID/Ref、recordedAt/decidedAt 或覆盖计算出的 Idempotency Key。Repository 必须在唯一约束下原子 compute-key、insert-or-compare 和生成首次 ID/Time;并发、不同到达时间或重放都返回同一记录。比较对象是 Contracts 定义的 Canonical Candidate,不是包含数据库生成字段的整行。测试必须覆盖两个 Worker 以不同到达时间并发提交相同 Candidate、调用方伪造 ID/Time/Key、同 Key 异 Candidate,以及首次响应丢失后的重放。

两条 normalizationPolicyVersion 来源严格分离:MeterEvent 取冻结 Billing Policy 内的 meteringNormalizationPolicyVersion,ProviderCostFact 取 Route Snapshot/Cost Evidence 的 providerCostNormalizationPolicyVersion。两者都固定本次单位与口径转换规则,均不得回查当前版本或互相代替;Contracts 测试必须覆盖策略发布后才首次到达的 Usage/Cost Evidence 仍使用执行前冻结版本。occurredAt 是执行/来源时间,只用于事件展示;recordedAt 是 Metering 权威追加时间,账务处理、Retention 与更正链顺序不得依赖可由上游声明或迟到的 occurredAt

MeterEvent 与 ProviderCostFact 都只允许追加式更正,不得原地覆盖。Billing 在 Reservation 终局前只读取经验证的 MeterEvent Current Head 构造 Finalization Manifest,不写客户账;Finalization 原子创建 Base 后,才可对获准的新 Head 目标差额追加一次幂等 Adjustment/Refund,不能重做或删除原 Settlement。Contracts/Repository 负向与乱序测试必须覆盖同键异 Payload、Eligibility-only 更正、终局前/后的四类资格转换、跨 Reservation/Attempt/Actor/Tenant/Offering/Price/Policy/Dimension、跨 Gateway Deployment/Binding/Route、跨 Source Dimension/Cost Mapping、跨币种/Cost Unit、错误 Ref/Schema Version、分叉、循环和旧 Head 晚到,并证明不会重复结算或污染供应毛利。

模型供应商成本、OceanWay 用户售价和最终财务结算是三个可独立对账的事实,不能只保存一个“扣了多少积分”。

其中 OceanWay Rate Card、Reservation 与追加式 Ledger 是用户账务事实源;Text/Media Gateway 的 ProviderUsageEvidence / ProviderCostEvidence 和供应商账单 source ref 只构成 Metering 的输入证据。ProviderCostFact 只用于供应核对、成本归集与毛利分析,不能驱动客户扣款;成本 Evidence/Fact 迟到、缺失或更正也不能阻塞、改变或重算已经由规范 MeterEvent 与 Price Snapshot 决定的客户费用。证据或事实无法闭合时进入对应对账,不得由网关余额、Observation、Telemetry 或内部价格推算用户扣款。

付款方与预算

Run 创建时固定 billingAccountId,执行中不得更改:

  • 个人空间任务使用个人 Billing Account;
  • 企业 Project 使用企业 Billing Account;
  • Workspace 与 Project 可以获得预算;
  • 成员、Agent、Service Account 与 API Key 可以获得更细的硬/软限制;
  • 组织余额不足时禁止自动改扣成员个人钱包;
  • Agent 调用子 Agent 时沿用同一个预算树,不能重新获得完整额度。

预算至少支持:

  • 单次上限;
  • 每日、每月或合同周期上限;
  • 软预警与硬阻断;
  • 允许产品、模型、Agent 和环境;
  • 审批阈值;
  • 已用、预占、待对账和可用额度;
  • 预算责任人与通知对象。

高成本任务在执行前展示付款主体、预计区间和预算影响。API 请求无法交互确认时,使用预先配置的硬限制和明确错误响应。

执行、资产与账务对账

普通失败任务、财务差异、对账信号和正式 Case 不是同一个概念。Provider、Run、Asset、Usage 与 Billing 事实无法自然闭合时,系统先在唯一 Owner 内保存明确状态:可重试暂态不写领域冲突事实;确定性矛盾写内容寻址 Conflict Fact 与 Active Blocked Work;影响扩大时再形成 Error/Alert 或 Incident。以下情况都需要诊断,但首期不会因此自动创建 Operations Case:

  • Provider 是否创建任务未知,Reservation 长期未终态;
  • Provider 成功但结果未取回或 Asset 未登记;
  • Asset 已登记但 Run 或 Settlement 未完成;
  • Usage 缺失、实际费用超过预占或供应账单不一致;
  • 重复 Provider Attempt、重复 Meter Event 或疑似重复结算;
  • 用户已退款但 Provider 成本已经发生;
  • Poll 与未来 Callback 的终态或 Usage 冲突。

正式 Reconciliation Case 只有两个 sourceScope

sourceScope.kind粒度请求来源唯一业务终态 Authority
late_settlement_dimensionReservation / Step / Attempt / Charge DimensionBilling-owned LateSettlementExposureBillingCaseResolutionApplied@1
billing_finalization_runReservation / Run / Finalization FenceBilling Finalization DecisionFinalizationReconciliationResolutionApplied@1

Provider Cost、Gateway、Execution、Asset、Webhook 与普通客户计量异常不得被塞入这两个分支。未来新增正式 Case 类型必须同时发布 Canonical Event、Mandatory core_operations Delivery、严格 Owner Read、Case Identity、终态 Authority、响应丢失恢复与投影契约。

对账人员只能提出和执行已经由相应 Owner 注册的受控领域命令。首期 Case-scoped 写命令只有 Late Settlement 执行与 Finalization 重新评估;查询原任务、重新取回结果、投影重建或 Webhook 重放属于只读诊断、Runbook 或未来独立 Incident-scoped Command,不继承 Billing Case 权限。平台承担/核销仍是待设计能力。禁止直接编辑 Reservation、余额或 Ledger 行;账务修复只追加可追溯分录。涉及金额或客户影响的动作按策略进行职责分离、审批和审计。

Reconciliation Case 由管理员平台与运维控制面管理流程与证据,Billing Service 仍是 Reservation、Settlement、Refund 和 Ledger 的唯一事实源。

商业商品模型

商品由不可变 Benefit Snapshot 描述,避免付款后因运营编辑而改变已购权益:

商品类型可包含内容
充值包充值积分
产品订阅产品授权、席位、套餐积分、定向权益、并发与企业功能
资源包图片/视频/音频权益、存储、并发或指定能力
企业套餐席位、产品组合、额度、SLA、支持与授信
FDE 合同里程碑、交付物、验收、服务期与后续平台权益

订阅生命周期需要明确生效、续费、升级、降级、取消、过期和补差价规则。订阅授权与可消费积分分开,取消工作台订阅不应自动抹掉合法剩余的充值积分。

购买入口

购买与介绍分为三层:

  1. 公共 /pricing:解释产品组合、适合对象、计量方式、企业方案和常见问题。
  2. console.oceanway.tech 统一客户控制台:完成充值、订阅、资源包、席位、付款方式、账单、发票、预算和合同查看。
  3. 产品内升级入口:显示当前任务相关的升级建议,跳转到统一购买流程并带回目标商品,不在每个产品复制一套商城。

购买完成后,Entitlement 由统一服务发放,所有平台读取同一事实源。

Console 是购买和账户管理界面,不是账务事实源。完整页面边界、产品回跳和企业权限见统一客户控制台

产品订阅与共享积分

推荐原则是“产品授权可独立,积分可跨平台”:

  • Canvas、漫剧、电商可以有独立订阅和席位;
  • 订阅决定能否进入某项专业功能、并发和企业能力;
  • 同一 Billing Account 的通用积分可支付所有已允许产品的模型用量;
  • 定向权益只用于匹配的能力;
  • 企业管理员可以限制某产品或某 Project 的消费。

这样既能清楚销售不同产品,又不会让用户在五个平台之间充值和搬运余额。

第三方费用

MCP 可能触发广告费、运费、采购款或 SaaS 自身用量。它们与 OceanWay 模型积分不是同一类费用:

  • OceanWay 账本记录自身模型、运行和平台服务费用;
  • 第三方费用记录 Provider、外部账户、预估/实际金额和责任主体;
  • 涉及真实资金或不可逆费用时需要额外审批;
  • 界面不得把第三方费用伪装成 OceanWay 积分扣款;
  • 对账和发票责任按合同明确。

FDE 商业边界

FDE 项目通常包含售前、合同、里程碑、变更单、发票、回款和验收,不适合直接用普通积分钱包结算:

  • FDE 专业服务费走合同、订单与发票;
  • 项目期间需要的平台模型用量可以绑定合同额度或企业 Billing Account;
  • 里程碑付款不会伪装成钱包充值;
  • 交付完成后的持续 API/工作台使用进入组织订阅与账本;
  • 公共案例发布与客户付款状态完全分离。

FDE 的私有里程碑、交付物、验收和合同权益摘要通过 Console 的 Delivery Workspace 展示;oceanway.tech/fde/cases 只承担公开说明和获得授权的案例发布。私有交付数据不得因付款、验收或项目结束自动进入公开页面。

账务和合规不变量

  • 每个 Billing Account 只有一套追加式账本,禁止直接改余额。
  • 用量事实、价格快照、Reservation 和结算分录不可变且可追溯。
  • 展示精度与存储精度分离;页面两位摘要不能写回精确账本。
  • 网页和 API 共享计费账户,不表示它们使用同一种认证凭据。
  • Key Quota、项目预算和成员额度不是子钱包。
  • 付款方在执行前固定,任何失败都不能静默换钱包。
  • 退款必须关联原订单、权益发放与消费事实,并保持幂等。
  • 管理员不能直接改余额、Reservation 或 Ledger;所有对账处置通过幂等 Billing Command 并产生 Audit。
  • Incident 恢复后必须扫描受影响的 Reservation、Meter Event、Settlement、Refund 与 Provider Cost Fact。
  • 企业合同、第三方费用和 OceanWay 积分使用不同会计事实。

On this page